[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::functionIS 내가 읽을로, 오버로드 확인을 고려하지 말라고 이 다른 포스트 .

이러한 종류의 솔루션을 강요 한 기술적 한계를 완전히 이해하지 못했습니다.

나는 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){}


답변