std :: function이 과부하 해결에 참여하지 않는 이유는 무엇입니까?


17

다음 코드는 컴파일되지 않는다는 것을 알고 있습니다.

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 ++에 익숙하지 않음)에게 설명 했으므로 어떤 번역 단계에서 위의 컴파일이 실패합니까?


1
@Evg 그러나 OPs 예제에서 허용되는 하나의 과부하 만 있습니다. 귀하의 예에서 둘 다 일치합니다
idclev 463035818

답변:


13

이것은 실제로 "번역 단계"와 관련이 없습니다. 순전히의 생성자에 관한 것입니다 std::function.

참조는 std::function<R(Args)>주어진 함수가 필요가 없습니다 정확히 유형의 R(Args). 특히, 함수 포인터가 필요하지 않습니다. 호출 가능한 유형 (멤버 함수 포인터, 과부하가있는 일부 객체)을 사용할 수 있습니다operator() 너무 오래가 호출 가능한처럼) 처럼 그것을했다 Args매개 변수와 반환 뭔가 로 전환 R (경우 또는 R이다 void, 그것은 아무것도 반환 할 수 있습니다).

이를 위해 적절한 생성자 std::function필수가 될 템플릿 : template<typename F> function(F f);. 즉, 모든 기능 유형을 사용할 수 있습니다 (위의 제한 사항에 따름).

식은 baz과부하 세트를 나타냅니다. 해당 표현식을 사용하여 과부하 세트를 호출하면 좋습니다. 특정 함수 포인터를 사용하는 함수에 대한 매개 변수로 해당 표현식을 사용하는 경우 C ++은 과부하 세트를 단일 호출로 정지 시켜서 올바르게 만들 수 있습니다.

그러나 함수가 템플릿이고 템플릿 인수 공제를 사용하여 해당 매개 변수가 무엇인지 파악하면 C ++은 더 이상 과부하 세트의 올바른 과부하가 무엇인지 결정할 수 없습니다. 따라서 직접 지정해야합니다.


내가 잘못했다면 용서하십시오. 그러나 C ++은 어떻게 사용 가능한 과부하를 구별 할 수 있습니까? std :: function <T>는 호환 가능한 유형을 허용하지만 정확히 일치 할뿐만 아니라 지정된 두 매개 변수를 사용하는 것처럼 두 baz () 오버로드 중 하나만 호출 할 수 있습니다. 명확하게하는 것이 왜 불가능합니까?
TuRtoise

모든 C ++에서 볼 수있는 것은 내가 인용 한 서명입니다. 그것이 무엇과 일치해야하는지 모른다. 본질적으로, C ++ 코드 (해당 템플릿 선언 인 코드) 에서 정답이 무엇인지 명확하지 않은 것에 대해 오버로드 세트를 사용할 때마다 언어는 사용자가 의미하는 바를 철자해야합니다.
Nicol Bolas

1
@TuRtoise : function클래스 템플릿 의 템플릿 매개 변수 는 관련없습니다 . 중요한 것은 호출 하는 생성자 의 템플릿 매개 변수입니다 . 그냥 typename F: 일명, 모든 유형.
Nicol Bolas

1
@TuRtoise : 당신이 무언가를 오해하고 있다고 생각합니다. 템플릿이 작동하는 방식이므로 "F"입니다. 서명에서 함수 생성자는 모든 유형을 취하므로 템플리트 인수 추론으로 호출하려고하면 매개 변수에서 유형이 추론됩니다. 과부하 세트에 공제를 적용하려는 시도는 컴파일 오류입니다. 컴파일러는 세트에서 가능한 모든 유형을 추론하여 어떤 유형이 해결되는지 확인하지 않습니다.
Nicol Bolas

1
"오버로드 세트에 공제를 적용하려는 시도는 컴파일 오류입니다. 컴파일러는 세트에서 가능한 모든 유형을 추론하여 어떤 유형이 문제를 일으키는 지 확인하지 않습니다." 내가 잃어버린 것, 감사합니다 :)
TuRtoise

7

과부하 해결은 (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)...); }; \
    } \
  }

5

여기서 문제는 컴파일러가 포인터 붕괴 기능을 수행하는 방법을 알려주는 것이 없다는 것입니다. 당신이 가지고 있다면

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

단일 유형을 강제하고 추론을 허용합니다.


"baz가 오버로드 된 함수이므로 추론 할 수있는 단일 유형이 없지만"C ++ 14 "이 인수는 Args ... 및 리턴 유형 R에 대해 f가 호출 가능하지 않으면이 생성자는 과부하 해결에 참여하지 않습니다." 확실하지는 않지만, 이것이 모호성을 해결하기에 충분할 것으로 기대했습니다.
idclev 463035818

3
@ formerlyknownas_463035818 그러나 그것을 결정하려면 먼저 유형을 추론해야하며 과부하 된 이름이기 때문에 유형을 추론 할 수 없습니다.
NathanOliver 2014

1

시점에서 컴파일러는 std::function생성자 에게 전달할 과부하를 결정 하고 있습니다.std::function 생성자가 모든 유형을 취하도록 템플릿 화되어 있다는 것입니다. 오버로드를 모두 시도 할 수있는 기능이 없으며 첫 번째 것은 컴파일되지 않지만 두 번째는 컴파일되지 않습니다.

이 문제를 해결하는 방법은 컴파일러에게 원하는 과부하를 명시 적으로 알려주는 것입니다 static_cast.

Bar(std::function<void()> fun = static_cast<void(*)()>(baz)) : bazFn(fun){}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.