std :: enable_if для условной компиляции функции-члена - PullRequest
134 голосов
/ 07 августа 2011

Я пытаюсь заставить работать простой пример, чтобы понять, как использовать 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 ++ не удаляет вторую функцию-член и считает, что это должно быть перегрузкой.

Ответы [ 6 ]

104 голосов
/ 07 августа 2011

SFINAE работает только в том случае, если подстановка при выводе аргумента шаблона аргумента делает конструкцию некорректной.Там нет такой замены.

Я тоже об этом думал и пытался использовать std::is_same< T, int >::value и ! std::is_same< T, int >::value, что дает тот же результат.

Это потому, что когда создается экземпляр шаблона класса (которыйслучается, когда вы создаете объект типа Y<int> среди других случаев), он создает все объявления своих членов (не обязательно их определения / тела!).Среди них также его шаблоны участников.Заметьте, что тогда известен T, а !std::is_same< T, int >::value возвращает false.Таким образом, он создаст класс 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 происходит, как ожидалось.См. этот вопрос и соответствующий ответ о том, как это сделать.

70 голосов
/ 30 января 2014

Я сделал этот короткий пример, который также работает.

#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;
}

Прокомментируйте, если хотите, чтобы я уточнил.Я думаю, что код более или менее говорит само за себя, но опять же я сделал это так, что я могу ошибаться:)

Вы можете увидеть это в действии здесь .

13 голосов
/ 30 сентября 2014

Для тех, кто опоздал и ищет решение, которое «просто работает»:

#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
7 голосов
/ 25 августа 2014

С этой записи:

Аргументы шаблона по умолчанию не являются частью подписи шаблона

Но можно сделать что-то подобное:

#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
}
5 голосов
/ 14 июня 2012

Одним из способов решения этой проблемы, специализацией функций-членов, является помещение специализации в другой класс, а затем наследование от этого класса. Возможно, вам придется изменить порядок наследования, чтобы получить доступ ко всем другим базовым данным, но этот метод работает.

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.
};

Недостаток этого метода заключается в том, что если вам нужно протестировать множество разных вещей для разных функций-членов, вам придется создать класс для каждой из них и поместить его в цепочку в дереве наследования. Это верно для доступа к общим элементам данных.

Ex:

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 голосов
/ 17 августа 2014

Логическое значение должно зависеть от выводимого параметра шаблона.Поэтому простой способ исправить это использовать булевский параметр по умолчанию:

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_MEMBER_REQUIRES из библиотеки Tick :

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
...