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

evnu
источник
1
Я не уверен, но я думаю, что это следующее: enable_if основан на SFINAE (ошибка замены не является ошибкой). Однако здесь у вас нет подстановки, потому что ни один параметр не может быть использован для определения того, какую перегрузку использовать. Вы должны сделать так, чтобы "true" и "false" зависели от T. (Я знаю, что вы не хотели делать это в простом примере, но сейчас это, вероятно, слишком просто ...)
Филипп
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класса шаблона, компилятор фактически не скомпилирует функции-члены шаблона; тем не менее, компилятор выполнит подстановку TDECLARATIONS в шаблоне элемента, чтобы эти шаблоны-члены могли быть созданы позже. Эта точка отказа не является SFINAE, потому что SFINAE применяется только при определении набора возможных функций для разрешения перегрузки , а создание экземпляра класса не является случаем определения набора функций для разрешения перегрузки. (Или так я думаю!)
Дан Ниссенбаум
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;
}

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

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

jpihl
источник
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просто пересылает тип шаблона класса T. Вы можете удалить шаблон класса Tследующим образом: cpp.sh/4nxw, но это своего рода побеждает цель.
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
user1284631
источник
6
Хм, почему вы переименовали std::enable_if_tв resolvedType.
Qwertie
1
Потому что не каждый может использовать C ++ 17 по причинам, которые могут сильно различаться.
Джеймс Ян
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 30.09.14
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_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
Пол Фульц II
источник
Это не сработало для меня таким образом. Maaybe что-то не хватает? Не могли бы вы переписать пример OP в рабочем виде?
user1284631 30.09.14
Исходный пример не работает с перегрузкой. Я обновил свой ответ, как вы можете сделать это с перегрузкой.
Пол Фульц 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();
}
Aedoro
источник