[c++] std :: function이 과부하 해결에 참여하지 않는 이유는 무엇입니까?
다음 코드는 컴파일되지 않는다는 것을 알고 있습니다.
void baz(int i) { }
void baz() { }
class Bar
{
std::function<void()> bazFn;
public:
Bar(std::function<void()> fun = baz) : bazFn(fun){}
};
int main(int argc, char **argv)
{
Bar b;
return 0;
}
때문에 std::function
IS 내가 읽을로, 오버로드 확인을 고려하지 말라고 이 다른 포스트 .
이러한 종류의 솔루션을 강요 한 기술적 한계를 완전히 이해하지 못했습니다.
나는 cppreference 의 번역 단계 와 템플릿 에 대해 읽었지만 반례를 찾을 수 없다는 어떤 추론도 생각할 수 없습니다. 하프 레이맨 (여전히 C ++에 익숙하지 않음)에게 설명 했으므로 어떤 번역 단계에서 위의 컴파일이 실패합니까?
답변
이것은 실제로 “번역 단계”와 관련이 없습니다. 순전히의 생성자에 관한 것입니다 std::function
.
참조는 std::function<R(Args)>
주어진 함수가 필요가 없습니다 정확히 유형의 R(Args)
. 특히, 함수 포인터가 필요하지 않습니다. 호출 가능한 유형 (멤버 함수 포인터, 과부하가있는 일부 객체)을 사용할 수 있습니다operator()
너무 오래가 호출 가능한처럼) 처럼 그것을했다 Args
매개 변수와 반환 뭔가 로 전환 R
(경우 또는 R
이다 void
, 그것은 아무것도 반환 할 수 있습니다).
이를 위해 적절한 생성자 std::function
필수가 될 템플릿 : template<typename F> function(F f);
. 즉, 모든 기능 유형을 사용할 수 있습니다 (위의 제한 사항에 따름).
식은 baz
과부하 세트를 나타냅니다. 해당 표현식을 사용하여 과부하 세트를 호출하면 좋습니다. 특정 함수 포인터를 사용하는 함수에 대한 매개 변수로 해당 표현식을 사용하는 경우 C ++은 과부하 세트를 단일 호출로 정지 시켜서 올바르게 만들 수 있습니다.
그러나 함수가 템플릿이고 템플릿 인수 공제를 사용하여 해당 매개 변수가 무엇인지 파악하면 C ++은 더 이상 과부하 세트의 올바른 과부하가 무엇인지 결정할 수 없습니다. 따라서 직접 지정해야합니다.
답변
과부하 해결은 (a) 함수 / 연산자의 이름을 호출하거나 (b) 명시 적 서명으로 포인터를 함수 (또는 함수 또는 멤버 함수)로 캐스팅 할 때만 발생합니다.
여기서도 발생하지 않습니다.
std::function
서명과 호환되는 모든 객체를 가져옵니다 . 구체적으로 함수 포인터를 사용하지 않습니다. 람다는 std 함수가 아니며 std 함수는 람다가 아닙니다.
이제 내 homebrew 함수 변형에서 서명을 R(Args...)
위해R(*)(Args...)
위해 정확히 이런 이유로 인수 (정확한 일치)를 . 그러나 “대응”서명보다 “정확한 일치”서명을 높이는 것을 의미합니다.
핵심 문제는 과부하 세트가 C ++ 객체가 아니라는 것입니다. 오버로드 세트의 이름을 지정할 수 있지만 “네이티브”로 전달할 수는 없습니다.
이제 다음과 같은 함수의 유사 과부하 세트를 작성할 수 있습니다.
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
#define OVERLOADS_OF(...) \
[](auto&&...args) \
RETURNS( __VA_ARGS__(decltype(args)(args)...) )
이렇게하면 함수 이름에서 오버로드 확인을 수행 할 수있는 단일 C ++ 객체가 생성됩니다.
매크로를 확장하면 다음과 같은 이점이 있습니다.
[](auto&&...args)
noexcept(noexcept( baz(decltype(args)(args)...) ) )
-> decltype( baz(decltype(args)(args)...) )
{ return baz(decltype(args)(args)...); }
쓰는 것이 귀찮습니다. 더 간단하고 약간 덜 유용한 버전이 있습니다.
[](auto&&...args)->decltype(auto)
{ return baz(decltype(args)(args)...); }
우리는 많은 수의 인수를 취한 다음에 완벽하게 전달하는 람다가 있습니다 baz
.
그때:
class Bar {
std::function<void()> bazFn;
public:
Bar(std::function<void()> fun = OVERLOADS_OF(baz)) : bazFn(fun){}
};
공장. 우리 fun
는 전달하는 대신에 저장하는 람다에 과부하 해상도를 연기합니다.fun
오버로드 세트를 직접 하는 (해결할 수없는) 시킵니다.
함수 이름을 오버로드 세트 오브젝트로 변환하는 C ++ 언어로 오퍼레이션을 정의하기위한 제안이 하나 이상 있습니다. 이러한 표준 제안서가 표준에 포함될 때까지 OVERLOADS_OF
매크로가 유용합니다.
한 걸음 더 나아가서 호환 기능 캐스트 포인터를 지원할 수 있습니다.
struct baz_overloads {
template<class...Ts>
auto operator()(Ts&&...ts)const
RETURNS( baz(std::forward<Ts>(ts)...) );
template<class R, class...Args>
using fptr = R(*)(Args...);
//TODO: SFINAE-friendly support
template<class R, class...Ts>
operator fptr<R,Ts...>() const {
return [](Ts...ts)->R { return baz(std::forward<Ts>(ts)...); };
}
};
그러나 그것은 모호해지기 시작했다.
라이브 예 .
#define OVERLOADS_T(...) \
struct { \
template<class...Ts> \
auto operator()(Ts&&...ts)const \
RETURNS( __VA_ARGS__(std::forward<Ts>(ts)...) ); \
\
template<class R, class...Args> \
using fptr = R(*)(Args...); \
\
template<class R, class...Ts> \
operator fptr<R,Ts...>() const { \
return [](Ts...ts)->R { return __VA_ARGS__(std::forward<Ts>(ts)...); }; \
} \
}
답변
여기서 문제는 컴파일러가 포인터 붕괴 기능을 수행하는 방법을 알려주는 것이 없다는 것입니다. 당신이 가지고 있다면
void baz(int i) { }
void baz() { }
class Bar
{
void (*bazFn)();
public:
Bar(void(*fun)() = baz) : bazFn(fun){}
};
int main(int argc, char **argv)
{
Bar b;
return 0;
}
그런 다음 코드는 작동하므로 구체적인 유형이 할당되어 있으므로 컴파일러가 원하는 기능을 알고 있습니다.
사용할 때 std::function
양식을 가진 함수 객체 생성자를 호출합니다.
template< class F >
function( F f );
템플릿이기 때문에 전달 된 객체의 유형을 추론해야합니다. 이후 baz
오버로드 기능은이 템플릿 공제가 실패 할 수 있도록 추론 할 수있는 하나의 유형은 없다 당신은 오류가 발생합니다. 당신은 사용해야 할 것입니다
Bar(std::function<void()> fun = (void(*)())baz) : bazFn(fun){}
단일 유형을 강제하고 추론을 허용합니다.
답변
시점에서 컴파일러는 std::function
생성자 에게 전달할 과부하를 결정 하고 있습니다.std::function
생성자가 모든 유형을 취하도록 템플릿 화되어 있다는 것입니다. 오버로드를 모두 시도 할 수있는 기능이 없으며 첫 번째 것은 컴파일되지 않지만 두 번째는 컴파일되지 않습니다.
이 문제를 해결하는 방법은 컴파일러에게 원하는 과부하를 명시 적으로 알려주는 것입니다 static_cast
.
Bar(std::function<void()> fun = static_cast<void(*)()>(baz)) : bazFn(fun){}