멤버 함수를 조건부로 컴파일하기위한 std :: enable_if


156

사용법을 이해하기 위해 간단한 예제를 얻으려고합니다 std::enable_if. 이 답변을 읽은 후 간단한 예를 제시하기가 너무 어렵지 않아야한다고 생각했습니다. std::enable_if두 멤버 함수 중에서 선택하고 그중 하나만 사용할 수 있도록 사용하고 싶습니다 .

불행히도, 다음은 gcc 4.7로 컴파일되지 않으며 몇 시간 동안 노력한 후에 너희들에게 내 실수가 무엇인지 묻고있다.

#include <utility>
#include <iostream>

template< class T >
class Y {

    public:
        template < typename = typename std::enable_if< true >::type >
        T foo() {
            return 10;
        }
        template < typename = typename std::enable_if< false >::type >
        T foo() {
            return 10;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

gcc는 다음과 같은 문제를보고합니다.

% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x    enable_if.cpp   -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'

왜 g ++가 두 번째 멤버 함수에 대한 잘못된 인스턴스를 삭제하지 않습니까? 표준에 따르면 std::enable_if< bool, T = void >::type부울 템플릿 매개 변수가 true 인 경우에만 존재합니다. 그러나 왜 g ++는 이것을 SFINAE로 간주하지 않습니까? 오버로드 오류 메시지는 g ++이 두 번째 멤버 함수를 삭제하지 않으며 이것이 오버로드되어야한다고 믿는 문제에서 비롯된 것으로 생각합니다.


1
확실하지 않지만 다음과 같습니다. enable_if는 SFINAE를 기반으로합니다 (대체 실패는 오류가 아닙니다). 그러나 사용할 과부하를 결정하는 데 매개 변수를 사용할 수 없기 때문에 여기서 대체 할 수는 없습니다. "참"과 "거짓"은 T에 의존해야합니다. (단순한 예에서는 그렇게하고 싶지 않지만 아마도 너무 간단합니다 ...)
Philipp

3
나도 그 생각하고 사용하려 std::is_same< T, int >::value하고 ! std::is_same< T, int >::value있는이 같은 결과를 제공합니다.
evnu

답변:


117

SFINAE는 템플리트 인수의 인수 공제를 대체하여 구문이 잘못된 경우에만 작동합니다. 그러한 대체는 없습니다.

나도 그 생각하고 사용하려 std::is_same< T, int >::value하고 ! std::is_same< T, int >::value있는이 같은 결과를 제공합니다.

클래스 템플릿이 인스턴스화 될 때 ( Y<int>다른 경우 중 유형의 객체를 만들 때 발생 ) 모든 멤버 선언을 인스턴스화해야합니다 (정의 / 본문은 아닙니다!). 그중에는 멤버 템플릿도 있습니다. T그때 알려진 사실 이며 !std::is_same< T, int >::value거짓이됩니다. 그래서 그것은 Y<int>포함 하는 클래스 를 만들 것입니다

class Y<int> {
    public:
        /* instantiated from
        template < typename = typename std::enable_if< 
          std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< true >::type >
        int foo();

        /* instantiated from

        template < typename = typename std::enable_if< 
          ! std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< false >::type >
        int foo();
};

std::enable_if<false>::type그 선언이 잘못 형성되도록, 존재하지 않는 유형에 액세스합니다. 따라서 프로그램이 유효하지 않습니다.

enable_if멤버 템플릿 자체가 멤버 템플릿의 매개 변수에 종속 되도록해야 합니다. 그러면 전체 유형이 여전히 종속적이므로 선언이 유효합니다. 이 중 하나를 호출하려고하면 템플릿 인수에 대한 인수 공제가 발생하고 SFINAE가 예상대로 발생합니다. 이 질문 과 그에 대한 답변을 참조하십시오 .


14
... 명확하게 설명하면 유용합니다. Y템플릿 클래스 의 인스턴스 가 인스턴스화되면 컴파일러는 실제로 템플릿 멤버 함수를 컴파일하지 않습니다. 그러나 컴파일러 T는 이러한 멤버 템플릿을 나중에 인스턴스화 할 수 있도록 멤버 템플릿 DECLARATIONS로 대체 합니다. SFINAE는 과부하 해결을 위한 가능한 함수 세트를 결정할 때만 적용 되고 클래스를 인스턴스화하는 것은 과부하 해결을 위한 함수 세트를 결정하는 경우가 아니기 때문에이 실패 지점은 SFINAE 가 아닙니다. (또는 그렇게 생각합니다!)
Dan Nissenbaum

93

나는이 짧은 예를 만들었습니다.

#include <iostream>
#include <type_traits>

class foo;
class bar;

template<class T>
struct is_bar
{
    template<class Q = T>
    typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
    {
        return true;
    }

    template<class Q = T>
    typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
    {
        return false;
    }
};

int main()
{
    is_bar<foo> foo_is_bar;
    is_bar<bar> bar_is_bar;
    if (!foo_is_bar.check() && bar_is_bar.check())
        std::cout << "It works!" << std::endl;

    return 0;
}

내가 자세히 설명하고 싶은 경우 의견을 말하십시오. 나는 코드가 다소 자명하다고 생각하지만 다시 한 번 틀렸다. :)

여기 에서 실제로 볼 수 있습니다 .


2
이것은 VS2012에서 컴파일되지 않습니다. error C4519: default template arguments are only allowed on a class template.
PythonNut

1
운이 없었어. 나는 gcc로만 테스트했습니다. 아마 이것이 도움이 될 것입니다 : stackoverflow.com/a/17543296/660982
jpihl

1
이것은 확실히 가장 좋은 대답이며 내가 찾던 것입니다.
Weipeng L

3
왜 다른 템플릿 클래스를 생성 할 필요가있다 Q이 동일하더라도 T?
ilya1725

1
test멤버 함수 를 템플릿해야 합니다. 둘 다 동시에 존재할 수는 없습니다. Q클래스 템플릿 type을 전달합니다 T. cpp.sh/4nxwT 와 같이 클래스 템플릿을 제거 할 수는 있지만 그 목적을 상실 합니다.
jpihl

13

"작동하는"솔루션을 찾고있는 후발 자들에게 :

#include <utility>
#include <iostream>

template< typename T >
class Y {

    template< bool cond, typename U >
    using resolvedType  = typename std::enable_if< cond, U >::type; 

    public:
        template< typename U = T > 
        resolvedType< true, U > foo() {
            return 11;
        }
        template< typename U = T >
        resolvedType< false, U > foo() {
            return 12;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

다음과 같이 컴파일하십시오.

g++ -std=gnu++14 test.cpp 

러닝은 다음을 제공합니다.

./a.out 
11

6
음, 왜 이름을 바꿀 것 std::enable_if_tresolvedType.
Qwertie

1
모두가 다를 수있는 이유로 C ++ 17을 사용할 수있는 것은 아니기 때문입니다.
James Yang

9

에서 게시물 :

기본 템플릿 인수는 템플릿 서명의 일부가 아닙니다

그러나 다음과 같이 할 수 있습니다.

#include <iostream>

struct Foo {
    template < class T,
               class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
    void f(const T& value)
    {
        std::cout << "Not int" << std::endl;
    }

    template<class T,
             class std::enable_if<std::is_integral<T>::value, int>::type = 0>
    void f(const T& value)
    {
        std::cout << "Int" << std::endl;
    }
};

int main()
{
    Foo foo;
    foo.f(1);
    foo.f(1.1);

    // Output:
    // Int
    // Not int
}

그것은 작동하지만 이것은 기본적으로 클래스 자체가 아닌 템플릿 함수입니다 ... 동일하게 프로토 타입 된 두 함수 중 하나를 오버로드 할 수 없습니다 (오버로드를 넘겨야 할 때). 그러나 아이디어는 좋습니다. OP 예제를 작업 양식으로 다시 작성할 수 있습니까?
user1284631

5

이 문제를 해결하는 한 가지 방법은 멤버 함수의 특수화를 다른 클래스에 특수화 한 다음 해당 클래스에서 상속하는 것입니다. 다른 모든 기본 데이터에 액세스하려면 상속 순서를 변경해야 할 수도 있지만이 기술은 작동합니다.

template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};

template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};

template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
    typedef FooImpl<T, boost::is_integer<T> > inherited;

    // you will need to use "inherited::" if you want to name any of the 
    // members of those inherited classes.
};

이 기술의 단점은 다른 멤버 함수에 대해 많은 다른 것들을 테스트해야 할 경우 각 클래스마다 클래스를 만들고 상속 트리에 연결해야한다는 것입니다. 공통 데이터 멤버에 액세스하는 경우에 해당됩니다.

전의:

template<class T, bool condition> class Goo;
// repeat pattern above.

template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
    typedef Goo<T, boost::test<T> > inherited:
    // etc. etc.
};

4

부울은 추론되는 템플릿 매개 변수에 의존해야합니다. 따라서 수정하는 쉬운 방법은 기본 부울 매개 변수를 사용하는 것입니다.

template< class T >
class Y {

    public:
        template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
        T foo() {
            return 10;
        }

};

그러나 멤버 함수를 오버로드하려는 경우에는 작동하지 않습니다. 대신 Tick 라이브러리 TICK_MEMBER_REQUIRES에서 사용하는 것이 가장 좋습니다 .

template< class T >
class Y {

    public:
        TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

        TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

};

또한 다른 라이브러리를 사용하지 않으려는 경우를 위해 자신의 멤버에 다음과 같은 매크로가 필요합니다.

template<long N>
struct requires_enum
{
    enum class type
    {
        none,
        all       
    };
};


#define MEMBER_REQUIRES(...) \
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type

그것은 저에게 그런 식으로 작동하지 않았습니다. 아마도 뭔가 빠졌습니까? OP 예제를 작업 양식으로 다시 작성할 수 있습니까?
user1284631

원래 예제는 오버로드에서 작동하지 않습니다. 오버로드로 어떻게 할 수 있는지 내 답변을 업데이트했습니다.
Paul Fultz II

0

다음은 매크로를 사용한 미니멀리즘 예입니다. enable_if((...))더 복잡한 표현식을 사용할 때는 이중 괄호 를 사용 하십시오 .

template<bool b, std::enable_if_t<b, int> = 0>
using helper_enable_if = int;

#define enable_if(value) typename = helper_enable_if<value>

struct Test
{
     template<enable_if(false)>
     void run();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.