2015-07-22 7 views
2

У меня есть шаблон класса CFoo<T>. Я хочу разрешить неявные приведения к другим экземплярам CFoo, но только для тех, чей шаблонный аргумент является базовым классом T.SFINAE для включения оператора трансляции только от производного до базового класса

Я пытался использовать SFINAE, но ни один из моих попыток работали над любым компилятором я попробовал (VC 2012 или НКА):

#include <type_traits> 

template <class T> class CFoo { 
public: 
    template <class Q> operator 
    // typename std::enable_if<std::is_base_of<Q, T>::value, CFoo<Q>&>::type // SHOULD WORK? 
    // typename std::enable_if<1, CFoo<Q>&>::type        // SHOULD WORK? 
    CFoo<Q>& // compiles, but doesn't restrict on Q like I want 
    () const { 
     return *(CFoo<Q>*)this; 
    } 
}; 

class A {}; 
class B : public A {}; 

int main(int argc, char* argv[]) 
{ 
    CFoo<B> b; 
    CFoo<A>& a = b; 
    return 0; 
} 

Почему-либо из закомментированных попыток SFINAE не работать здесь? В обоих случаях я просто получаю сообщение об ошибке для недействительной инициализации a, как если бы мой оператор не вызывался.

+0

У этого _poof_ написано все это. –

+0

'Q' не находится в выведенном контексте. Но почему вы это делаете? –

+0

@ Капитан О. Я не понимаю! – johnnycrash

ответ

6

Согласно [temp.deduct.conv]:

Шаблон вывод аргумента делается путем сравнения тип возвращаемого шаблона функции преобразования (назовем его P) с типом, который необходим в результате (назовите это A, см. 8.5, 13.3.1.5 и 13.3.1.6 для определения этого типа), как описано в 14.8.2.5.

В простом случае:

template <class Q> 
operator CFoo<Q>& const; 

Это просто, мы пытаемся вывести CFoo<Q>& против CFoo<A>&. В этом разделе есть и другие правила, но в конечном итоге этот вывод преуспевает с Q == A.

Обе другие попытки не срабатывают по той же причине. Я заеду проще один:

template <class Q> 
operator typename std::enable_if<1, CFoo<Q>&>::type const; 

Здесь мы пытаемся вывести typename std::enable_if<1, CFoo<Q>&>::type. Это не выведенный контекст (это вложенный имя-спецификатор типа, который был указан с использованием идентификатора ), поэтому вывод не выполняется. Таким образом, эта функция преобразования не будет рассматриваться, поэтому присваивание не выполняется, поскольку преобразование не найдено.

Вам нужен тип возвращаемого быть выведен контекст, поэтому SFINAE должен идти здесь:

template <class Q, 
      typename = std::enable_if_t<std::is_base_of<Q, T>::value>> 
operator CFoo<Q>& const; 

Таким образом, у нас есть что-то вывести (CFoo<Q>&) - и вычет может быть успешным (если Q является основой T):

CFoo<A>& a = b; // OK 
CFoo<int>& i = b; // deduction failure on Q, so there's no viable conversion function 
        // so this is an error 

Тем не менее, в то время как я увлекся решение головоломок шаблона, а T.C. указывает, что это действительно не является хорошим решением, потому что:

return *(CFoo<Q>*)this; 

просто делает reinterpret_castconst_cast), так что на самом деле не может быть делать что-нибудь разумное, и вы почти наверняка (если CFoo не тривиальна) в конечном итоге с неопределенным поведением, пытаясь получить доступ к своим членам с неправильным типом.

Вы, вероятно, вместо этого нужно добавить Конверсиям конструктор вместо преобразования функции:

template <typename Q, 
      typename = std::enable_if_t<std::is_base_of<T, Q>::value>> 
CFoo(CFoo<Q> const&) { } 

Таким образом, когда вы делаете:

CFoo<A> a = b; // a is not a reference anymore 

Вы строите новый объект, который обязательно будет действителен.

+0

YOU ROCK! Теперь вы можете исправить C++, поэтому в версии C++ 20 мы используем эту потрясающую функцию фильтрации шаблонов и делаем ее ПРОСТОЙ. – johnnycrash

+0

@johnnycrash Что такое C++ 20? – Barry

+2

Это решает проблему дедукции, но большая проблема с кодом OP заключается в том, что практически любое использование возвращаемой ссылки вызывает UB. –