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існує лише тоді, коли параметр булевого шаблону є істинним. Але чому g ++ не вважає це SFINAE? Я думаю, що повідомлення про помилку при перевантаженні походить від проблеми, що g ++ не видаляє функцію другого члена і вважає, що це повинно бути перевантаженням.


1
Я не впевнений, але я думаю, що це наступне: enable_if заснований на SFINAE (збій заміни не є помилкою). Однак ви не маєте підстановки тут, оскільки жоден параметр не може бути використаний для визначення того, яке перевантаження використовувати. Ви повинні зробити так, щоб "справжнє" і "неправдиве" залежало від Т. (я знаю, ви цього не хотіли робити на простому прикладі, але це, мабуть, занадто просто ...)
Філіп

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в ДЕКЛАРАЦІЇ шаблону членів, щоб ці шаблони учасників могли бути інстанціровані пізніше. Цей пункт відмови не є 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просто пересилає тип шаблону класу 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

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
}

Це працює, але це в основному шаблонні функції, а не сам клас ... Це не дозволяє скинути одну з двох ідентично прототипованих функцій ні (коли вам потрібно передати перевантаження). Однак ідея приємна. Не могли б ви переписати приклад ОП у робочій формі, будь ласка?
користувач1284631

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

Це не працювало для мене таким чином. Мейбе щось не вистачає? Не могли б ви переписати приклад ОП у робочій формі, будь ласка?
користувач1284631

Оригінальний приклад не працює з перевантаженням. Я оновив свою відповідь, як можна це зробити з перевантаженням.
Пол Фульц 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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.