2016-08-06 2 views
2

Я хотел бы создать шаблонный класс test<T>, который я могу преобразовать в test<U> (возможно, неявно), если T конвертируется в U. Самая простая идея, которая пришла мне в голову, - добавить шаблонный конструктор, который принимает test<U>, где аргумент шаблона U находится под управлением enable_if.Преобразовать класс <T> к классу <U>, если T конвертируется в U

#include <iostream> 
#include <type_traits> 

template <typename T> 
class test { 
    int _foo; 
public: 
    int foo() const { return _foo; } 

    test() : _foo(5) {} 

    // This works: 

    // template <typename U> 
    // test(test<U> other) 
    // : _foo(other.foo()) {} 

    // This doesn't, can't resolve `U`: 

    template <typename U> 
    test(test<typename std::enable_if<std::is_convertible<U, T>::value, U>::type> other) 
    : _foo(other.foo()) {} 

}; 

int main() { 
    test<int> a; 
    test<long> b = a; // T = long, U = int 

    // true 
    std::cout << std::boolalpha << std::is_convertible<int, long>::value << std::endl; 

    return 0; 
} 

Если я просто объявляю первый шаблонный конструктор, код работает нормально. Со вторым конструктором, он не компилируется:

21:9: note: candidate template ignored: couldn't infer template argument 'U' 
    test(test<typename std::enable_if<std::is_convertible<U, T>::value, U>::type> other) 
    ^

Почему не компилятор делать вывод U в этом случае? Это выглядит довольно просто, я должен упустить что-то в выводе шаблона. Кроме того, если преобразование не может быть достигнуто таким образом, , что было бы лучшим способом сделать test<T> конвертируемых в test<U>?


Как примечание стороны, мне удалось получить его на работу, делая все test<T> друзей, и объявить оператор преобразования, который использует enable_if в реализации (как следует), но я все-таки хотел бы знать, почему первый, более простой подход не работает.

template <typename T> 
class test { 
    int _foo; 

    template <typename U> 
    friend class test; 

    test(int foo) : _foo(foo) {} 

public: 
    int foo() const { return _foo; } 

    test() : _foo(5) {} 

    template <typename U> 
    operator test<U>() const { 
     using return_t = typename std::enable_if<std::is_convertible<U, T>::value, U>::type; 
     return test<return_t>(_foo); 
    } 
}; 
+2

В некоторой степени важно: [Почему я должен избегать std :: enable_if в сигнатурах функций] (http://stackoverflow.com/questions/14600201/why-should-i-avoid-stdenable-if-in-function-signatures) , _ «Положить хак в параметры шаблона» _. – dfri

ответ

2

Why can't the compiler infer U in this case?

Тип вычет принципиально не может вывести T из типов параметров, таких как a<T>::b. Даже если у вас есть

template <typename T> struct identity { typedef T type; }; 

тогда компилятор еще не исключает, вы обеспечивая специализацию где-то, что скрытно делает identity<X>::typeY для некоторого типа X.

Это то же самое с std::enable_if: стандартные шаблоны классов библиотеки не получают специального лечения в правилах языка, поэтому компилятор не может понять, что если std::enable_if<cond, U>::type должен быть X, то U должен быть X.

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

Конструкторы не имеют типа возврата. К счастью, вызывающие не могут явно указывать аргументы шаблона конструктора, поэтому ставим std::enable_if в аргументе по умолчанию шаблона, как уже ответил m.s. там безопасно.

3

U появляется в non-deduced context.

Это решает ошибку компилятора:

template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value, U>::type> 
test(test<U> other) 
: _foo(other.foo()) {} 

live example

Смежные вопросы