2017-01-27 12 views
4

Очень странная проблема, с которой я боролся последние несколько часов (после решения 5-6 других проблем с SFINAE, поскольку я новичок в ней). В основном в следующем коде я хочу иметь f() работать для всех возможных конкретизации шаблона, но есть g() доступны только при N == 2:Почему SFINAE (enable_if) работает из определения внутреннего класса, но не извне

#include <type_traits> 
#include <iostream> 

template<typename T, int N> 
class A 
{ 
public: 
    void f(void); 
    void g(void); 
}; 

template<typename T, int N> 
inline void A<T, N>::f() 
{ 
    std::cout << "f()\n"; 
} 

template<typename T, int N, typename std::enable_if<N == 2, void>::type* = nullptr> 
inline void A<T, N>::g() 
{ 
    std::cout << "g()\n"; 
} 

int main(int argc, char *argv[]) 
{ 
    A<float, 2> obj; 
    obj.f(); 
    obj.g(); 

    return 0; 
} 

Когда я пытаюсь скомпилировать я получаю ошибку о том, 3 параметра шаблона вместо двух , Затем, после нескольких испытаний, я решил перенести определение g() внутри определения A самого, как это:

#include <type_traits> 
#include <iostream> 

template<typename T, int N> 
class A 
{ 
public: 
    void f(void); 

    template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr> 
    void g() 
    { 
     std::cout << "g()\n"; 
    } 
}; 

template<typename T, int N> 
inline void A<T, N>::f() 
{ 
    std::cout << "f()\n"; 
} 

int main(int argc, char *argv[]) 
{ 
    A<float, 2> obj; 
    obj.f(); 
    obj.g(); 

    return 0; 
} 

Теперь, волшебно все работает. Но мой вопрос: ПОЧЕМУ? Разве компилятор не видит, что внутри определения класса я пытаюсь встроить функцию-член, которая также зависит от трех параметров шаблона? Или давайте обратим вопрос: если он работает внутри определения A, почему он не работает на улице? Где разница? Разве нет еще 3 параметра, что на +1 больше, чем класс A для его параметров шаблона?

Кроме того, почему это работает, когда я делаю третий параметр не-тип один, а не тип один? Заметьте, что я действительно создаю указатель типа, возвращаемого enable_if, и присваиваю ему значение по умолчанию nullptr, но я вижу, что я не могу просто оставить его там как параметр типа, как в других сообщениях форума SO, которые я вижу здесь.

Цените его так много, спасибо !!!

ответ

2

Это было бы потому, что шаблонная функция в шаблоном классе имеет два множества параметров шаблона, а не один. "Правильная" форма, таким образом:

template<typename T, int N> 
class A 
{ 
public: 
    void f(void); 

    template<typename std::enable_if<N == 2, void>::type* = nullptr> 
    void g(void); 
}; 

template<typename T, int N>           // Class template. 
template<typename std::enable_if<N == 2, void>::type* /* = nullptr */> // Function template. 
inline void A<T, N>::g() 
{ 
    std::cout << "g()\n"; 
} 

в действии here.

[Обратите внимание, что это не фактически правильно, по причине, объясненной в нижней части этого ответа. Он сломается, если N != 2.]

Продолжите чтение для объяснения, если вы этого желаете.


Еще со мной? Ницца. Давайте рассмотрим каждую ситуацию, не так ли?

  1. Определение A<T, N>::g() вне A:

    template<typename T, int N> 
    class A 
    { 
    public: 
        void f(void); 
        void g(void); 
    }; 
    
    template<typename T, int N, typename std::enable_if<N == 2, void>::type* = nullptr> 
    inline void A<T, N>::g() 
    { 
        std::cout << "g()\n"; 
    } 
    

    В этом случае A<T, N>::g() 's шаблон декларация не соответствует A' s объявление шаблона. Поэтому компилятор испускает ошибку. Кроме того, g() не является шаблоном, поэтому шаблон нельзя разделить на шаблон класса и шаблон функции без изменения определения A.

    template<typename T, int N> 
    class A 
    { 
    public: 
        void f(void); 
    
        // Here... 
        template<typename std::enable_if<N == 2, void>::type* = nullptr> 
        void g(void); 
    }; 
    
    // And here. 
    template<typename T, int N>           // Class template. 
    template<typename std::enable_if<N == 2, void>::type* /* = nullptr */> // Function template. 
    inline void A<T, N>::g() 
    { 
        std::cout << "g()\n"; 
    } 
    
  2. Определение A<T, N>::g() внутри A:

    template<typename T, int N> 
    class A 
    { 
    public: 
        void f(void); 
    
        template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr> 
        void g() 
        { 
         std::cout << "g()\n"; 
        } 
    }; 
    

    В этом случае, поскольку g() определяется рядный, он неявно имеет A параметры шаблона «s, без необходимости указывать их вручную. Поэтому g() на самом деле:

    // ... 
        template<typename T, int N> 
        template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr> 
        void g() 
        { 
         std::cout << "g()\n"; 
        } 
    // ... 
    

В обоих случаях для g() иметь свои собственные параметры шаблона, в то же время член шаблонного класса, параметры шаблона функции должны быть отделены от шаблона класса параметры. В противном случае шаблон класса функции не будет соответствовать классу.


Теперь, когда мы рассмотрели, что, я должен отметить, что SFINAE касается только непосредственных параметров шаблона. Итак, для g() для использования SFINAE с N, N должен быть его параметром шаблона; в противном случае вы получите сообщение об ошибке, если попытаетесь позвонить, например, A<float, 3>{}.g(). Это может быть выполнено с помощью посредника, если это необходимо.

Кроме того, вам необходимо предоставить версию g(), которая может быть вызвана, когда N != 2. Это связано с тем, что SFINAE применим только в том случае, если существует хотя бы одна действительная версия функции; если никакая версия g() не может быть вызвана, тогда будет испущена ошибка, и никакая SFINAE не будет выполнена.

template<typename T, int N> 
class A 
{ 
public: 
    void f(void); 

    // Note the use of "MyN". 
    template<int MyN = N, typename std::enable_if<MyN == 2, void>::type* = nullptr> 
    void g(void); 

    // Note the "fail condition" overload. 
    template<int MyN = N, typename std::enable_if<MyN != 2, void>::type* = nullptr> 
    void g(void); 
}; 

template<typename T, int N> 
template<int MyN /*= N*/, typename std::enable_if<MyN == 2, void>::type* /* = nullptr */> 
inline void A<T, N>::g() 
{ 
    std::cout << "g()\n"; 
} 

template<typename T, int N> 
template<int MyN /*= N*/, typename std::enable_if<MyN != 2, void>::type* /* = nullptr */> 
inline void A<T, N>::g() 
{ 
    std::cout << "()g\n"; 
} 

Если вы сделаете это, мы сможем упростить ситуацию, если посредник сделает тяжелый подъем.

template<typename T, int N> 
class A 
{ 
public: 
    void f(void); 

    template<bool B = (N == 2), typename std::enable_if<B, void>::type* = nullptr> 
    void g(void); 

    template<bool B = (N == 2), typename std::enable_if<!B, void>::type* = nullptr> 
    void g(void); 
}; 

// ... 

Просмотреть в действии here.

+1

Также обратите внимание, что, как показано в [skypjack's answer] (http://stackoverflow.com/a/41904229/5386374): 1) 'std :: enable_if_t ' может использоваться как псевдоним типа для 'typename std :: enable_if :: type', 2) 'T' по умолчанию' void' и может быть опущен, если он не должен быть другим типом, и 3) 'std :: enable_if_t' может использоваться для возвращаемого типа функции (или на параметр, если функция имеет какой-либо). –

+0

Большое вам спасибо !!! Я все еще перевариваю этот кусок информации и переписываю то, что знал о шаблонах, теперь, когда я сталкиваюсь с этим никогда не замеченным миром SFINAE :) Кстати, знаете ли вы, почему MSVC дает мне ошибки везде, говоря «тип ': не является членом' std :: enable_if '??? Код, который я опубликовал, был скомпилирован с помощью некоторой онлайн-среды, основанной на gcc, но мой фактический проект находится в VC, и я вижу, что это как-то предупреждает меня о случае, когда чек будет ложным -> который duuhhh, вот почему я делаю для этого, к ТАКЖЕ имеют случаи, когда чек не пройдет. Любая идея, почему он идиот? :) – Otringal

+0

@Otringal Вы определили версию 'g()', которую можно вызвать, когда 'N! = 2'? Если вы этого не сделали, вы получите сообщение об ошибке при попытке вызвать 'g()' на 'A ', чье 'N' - это что-то другое, кроме 2, потому что' std :: enable_if' имеет только член 'type', если логическое условие' true'. –

3

В первом фрагменте параметры шаблона: A, и вы обновляете его дополнительным параметром (это ошибка).
Кроме того, выражения sfinae связаны с шаблоном шаблона или шаблоном функции, что не так в примере.

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


вытекает рабочую версию:

#include <type_traits> 
#include <iostream> 

template<typename T, int N> 
class A 
{ 
public: 
    void f(void); 

    template<int M = N> 
    std::enable_if_t<M==2> g(); 
}; 

template<typename T, int N> 
inline void A<T, N>::f() 
{ 
    std::cout << "f()\n"; 
} 

template<typename T, int N> 
template<int M> 
inline std::enable_if_t<M==2> A<T, N>::g() 
{ 
    std::cout << "g()\n"; 
} 

int main(int argc, char *argv[]) 
{ 
    A<float, 2> obj; 
    obj.f(); // ok 
    obj.g(); // ok (N==2) 

    A<double,1> err; 
    err.f(); // ok 
    //err.g(); invalid (there is no g()) 

    return 0; 
} 

Обратите внимание, что параметр не типа должен быть в реальном контексте выражения SFINAE для последнего на работу.
Из-за этого template<int M = N> является обязательным.

Другие решения также применяются.
В качестве примера вы можете использовать базовый класс, который экспортирует f и производный класс шаблонов с полной специализацией, которые добавляют g.

+1

Это выглядит правильно. В конечном счете, причина в том, что sfinae полезен только для включения или исключения * шаблона * определения. и в первом примере 'g' не является шаблоном; это функция-член класса шаблона. Выбрав 'g' функцию члена шаблона, вы можете включить или пропустить его по желанию через sfinae. – WhozCraig

+1

@WhozCraig Я украл ваши слова, чтобы обогатить ответ. Спасибо. :-) – skypjack

+0

Спасибо!Кроме того, знаете ли вы, почему это, по-видимому, работает только при использовании типа enable_if как указателя, а не самого базового типа? Например, если вместо :: type * = nullptr, я оставляю только :: type = 0, он не работает. Зачем? – Otringal