Условное определение конструктора в шаблонном классе - PullRequest
1 голос
/ 25 апреля 2020

Я хочу, чтобы следующее компилировалось:

#include <type_traits>

template<typename T>
class C {
public:
    C() {}
    C( const C &that ) {}
};

void foo( const C<const char> &c );

int main() {
    C<const int> i;

    foo( C<char>{} );
}

Конечно, это не так, поскольку foo ожидает C<const char>, и я передаю C<char>. Поэтому я хочу добавить неявное преобразование из C<T> в C<const T>. В первую очередь мы добавляем следующий конструктор к C:

C( const C< std::remove_const_t<T> > &that ) {}

Это не работает, потому что если T само по себе не является константой, это определение идентично конструктору копирования, который означает, что мы переопределяем один и тот же конструктор дважды:

so.cpp: In instantiation of ‘class C<char>’:
so.cpp:17:18:   required from here
so.cpp:9:5: error: ‘C<T>::C(const C<typename std::remove_const<_Tp>::type>&) [with T = char; typename std::remove_const<_Tp>::type = char]’ cannot be overloaded with ‘C<T>::C(const C<T>&) [with T = char]’
     C( const C< std::remove_const_t<T> > &that ) {}
     ^
so.cpp:7:5: note: previous declaration ‘C<T>::C(const C<T>&) [with T = char]’
     C( const C &that ) {}

Вторая попытка, попробуйте использовать enable_if:

template< std::enable_if_t< std::is_const_v<T>, int > = 0 >
C( const C< std::remove_const_t<T> > &that ) {}

, который не работает. Поскольку шаблон не зависит от какого-либо вывода, сделанного в аргументах конструктора, он оценивается слишком рано:

so.cpp: In instantiation of ‘class C<char>’:
so.cpp:18:18:   required from here
so.cpp:10:5: error: no type named ‘type’ in ‘struct std::enable_if<false, int>’
     C( const C< std::remove_const_t<T> > &that ) {}
     ^

Давайте искусственно заставим его зависеть от аргументов, тогда:

template<
        typename V,
        std::enable_if_t<
                std::is_const_v<T> && std::is_same_v<T, V>,
                int
        > = 0
>
C( const C< std::remove_const_t<V> > &that ) {}

Я не уверен почему это не удается. Я получаю следующую ошибку:

so.cpp: In function ‘int main()’:
so.cpp:24:10: error: invalid initialization of reference of type ‘const C<const char>&’ from expression of type ‘C<char>’
     foo( C<char>{} );
          ^~~~~~~~~
so.cpp:19:6: note: in passing argument 1 of ‘void foo(const C<const char>&)’
 void foo( const C<const char> &c );
      ^~~

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

Как мне решить эту проблему?

Ps Да, я знаю, что в этом случае Я могу использовать неявный конструктор копирования.

1 Ответ

1 голос
/ 25 апреля 2020

Вы можете использовать SFINAE для функции, но функция должна быть шаблоном, а условие должно зависеть от этого шаблона, помните, что T из класса является фиксированным для функции-члена:

template<typename T>
class C {
public:
    C() {}
    C( const C &that ) {}

    template <typename U,
              std::enable_if_t<std::is_same_v<U, std::remove_const_t<T>>, int> = 0>
    C( const C<U> &that ) {}
};

Демо

В C ++ 20 будет иметь хороший синтаксис:

template<typename T>
class C {
public:
    C() {}
    C( const C &that ) {}
    C( const C< std::remove_const_t<T> > &that ) requires(std::is_const<T>::value) {}
};

Для вашего кода:

template< std::enable_if_t< std::is_const_v<T>, int > = 0 >
C( const C< std::remove_const_t<T> > &that ) {}

не является контекстом замещения, параметр является фиксированным в зависимости от класса у вас есть серьезная ошибка вместо ожидаемого SFINAE.

Для

template<
        typename V,
        std::enable_if_t<
                std::is_const_v<T> && std::is_same_v<T, V>,
                int
        > = 0
>
C( const C< std::remove_const_t<V> > &that ) {}

Возможно, у вас SFINAE, но V не вычитается, поэтому вы должны предоставить его, но не может сделать это на вызывающем сайте, так как это конструктор.

Простое исправление - дать значение по умолчанию:

 template<
        typename V = T,
        std::enable_if_t<
                std::is_const_v<T> && std::is_same_v<T, V>,
                int
        > = 0
>
C( const C< std::remove_const_t<V> > &that ) {}

Демо

(как V необходим T, вы можете сбросить std::is_same_v<T, V>, но изменить std::is_const_v<T> на std::is_const_v<V>, чтобы получить SFINAE)

...