Reputation: 8573
I want the following to compile:
#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>{} );
}
Of course, it doesn't, as foo
expects C<const char>
, and I'm passing C<char>
. So I want to add an implicit conversion from C<T>
to C<const T>
. First attempt, we add the following constructor to C
:
C( const C< std::remove_const_t<T> > &that ) {}
That doesn't work, because if T
is not, itself, const, this definition is identical to the copy constructor, which means we redefine the same constructor twice:
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 ) {}
Second attempt, try to use enable_if
:
template< std::enable_if_t< std::is_const_v<T>, int > = 0 >
C( const C< std::remove_const_t<T> > &that ) {}
Which doesn't work. Since the template doesn't depend on any deduction done in the constructor's arguments, it is evaluated too early:
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 ) {}
^
Let's artificially make it depend on the arguments, then:
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 ) {}
I'm not sure why this fails. I get the following error:
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 );
^~~
I suspect that the deduction happens too deep for the compiler to understand what substitution it needs to make.
How do I resolve this?
P.s. Yes, I know that in this case I can use the implicit copy constructor.
Upvotes: 1
Views: 149
Reputation: 217245
You can use SFINAE on function but function should be template,
and condition should depend of that template, remember that T
from class is fixed for the member function:
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 ) {}
};
In C++20 would have nice syntax:
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) {}
};
For your code:
template< std::enable_if_t< std::is_const_v<T>, int > = 0 >
C( const C< std::remove_const_t<T> > &that ) {}
is not a Substitution context, parameter are fixed by class, you have hard error instead of your expected SFINAE.
For
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 ) {}
You might have SFINAE, but V
is not deducible, so you have to provide it, but cannot do it at calling site as it is a constructor.
Simple fix is to give default value:
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 ) {}
(as V is neccessary T, you might drop std::is_same_v<T, V>
but change std::is_const_v<T>
into std::is_const_v<V>
to have SFINAE)
Upvotes: 1