Mike Lischke
Mike Lischke

Reputation: 53492

Candidate template ignored: couldn't infer template argument

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

Answers (3)

Holt
Holt

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:

  • If 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);
  • Otherwise, you get:
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

rocambille
rocambille

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

skypjack
skypjack

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

Related Questions