Reputation: 53492
First: the error message is indeed as given. There is just quotes after the word "argument", which is strange on its own. But here's the problem I'm trying to solve. I'm writing a class that stores a reference of a (template) type internally and should also accept convertible types:
template<typename T>
class Ref {
public:
Ref();
Ref(std::nullptr_t);
explicit Ref(T *value);
Ref(Ref const& value);
template<typename T2, typename std::enable_if<std::is_convertible<T2*, T*>::value, T2>::type>
Ref(Ref<T2> const& value);
template<typename T2, typename std::enable_if<std::is_convertible<T2*, T*>::value, T2>::type>
Ref(Ref<T2> &&value);
private:
T *_value;
};
Now I have 2 classes A and B:
class A {
};
class B : public A {
};
and am trying to assign a Ref instance for B to a Ref variable for class A:
Ref<B> t;
Ref<A> t2(t);
This should actually compile, but I get the mentioned error (clang) for the last 2 constructors (those taking a convertible type), which should actually kick in for that assignment. What needs to be done to make template argument deduction work here?
Upvotes: 5
Views: 5829
Reputation: 37626
You are using std::enable_if
wrongly, this should be1 2:
template<typename T2,
typename =
typename std::enable_if<std::is_convertible<T2*, T*>::value>::type>
Ref(Ref<T2> const& value);
Here, the second template argument is defaulted to something that would fail if T2*
is not convertible to T1*
, which is what you want:
std::is_convertible<T2*, T*>::value
is true
, then this is equivalent to:template<typename T2, typename = void> // Ok, well-formed
Ref(Ref<T2> const& value);
template<typename T2, typename = /* Something ill-formed */>
Ref(Ref<T2> const& value);
In your original code, when the std::enable_if
was successful, your template was equivalent to:
template<typename T2, typename T2> // Well-formed, but T2 cannot be deduced
Ref(Ref<T2> const& value);
Which was not what you wanted (the compiler was not able to deduce both T2
in this case).
1 You can replace the typename std::enable_if<>::type
with std::enable_if_t<>
if you have access to C++14.
2 This is one possibility, the other one being to use std::enable_if_t<..., int> = 0
(or something alike), but I prefer the version with typename = ...
. They are not strictly equivalent in the general case, but in this precise case it does not matter.
Upvotes: 10
Reputation: 15996
To complete Holt's answer, note that you can merge your default constructor and your constructor by nullptr_t
into Ref(std::nullptr_t = nullptr):_value{nullptr} {}
.
Ref(Ref const& value);
is equivalent to Ref(Ref<T2> const& value);
whith T2=T
.
Your snippet misses operator=
. Using copy and swap idiom, you may use it to factorize the tests on T2
a little bit:
template<typename T>
class Ref {
public:
Ref(std::nullptr_t = nullptr):_value{nullptr} {}
explicit Ref(T *value);
template<typename T2, typename = typename std::enable_if<std::is_convertible<T2*, T*>::value, T2>::type>
Ref(Ref<T2> const& value);
template<typename T2>
Ref(Ref<T2> &&value):Ref{}
{
swap(*this, value);
}
template<typename T2>
Ref& operator=(Ref<T2> value)
{
swap(*this, value);
return (*this);
}
template<typename T1, typename T2, typename = typename std::enable_if<std::is_convertible<T2*, T1*>::value>::type>
friend void swap(Ref<T1>& t1, Ref<T2>& t2)
{
using std::swap;
swap(t1._value, t2._value);
}
private:
T *_value;
};
Upvotes: 2
Reputation: 50550
@Holt's answer is nice and correctly explains how you should use std::enable_if
.
Anyway, in this case, you don't need to use it at all.
A static_assert
is enough here and error messages will be nicer with it:
template<typename U>
Ref(Ref<U> const& value) {
static_assert(std::is_convertible<U, T>::value, "!");
// whatever you want
}
template<typename U>
Ref(Ref<U> &&value) {
static_assert(std::is_convertible<U, T>::value, "!");
// whatever you want
}
Now, something like this (where C
is not convertible to A
):
Ref<A> t3(Ref<C>{});
Will give you an error like this:
error: static assertion failed: ! static_assert(std::is_convertible::value, "!");
Sfinae expressions are usually used to (let me say) enable or disable choices.
If you don't have a set of valid alternatives from which to pick the right one up, a static_assert
is usually preferable.
Upvotes: 4