Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ambiguous partial specializations with std::enable_if

I have a problem encountered at a condtion like below:

#include <iostream>
#include <type_traits>

#define TRACE void operator()() const { std::cerr << "@" << __LINE__ << std::endl; }

template <class T>
struct check : std::true_type {};

template <class F, class T, class Check=void>
struct convert {
  TRACE;// first case
};

template <class F, class T>
struct convert<F*, T, typename std::enable_if<(check<F>::value && check<T>::value), void>::type> {
  TRACE; // second case
};

template <class T>
struct convert<int*, T, typename std::enable_if<(check<T>::value), void>::type> {
  TRACE; // third case
};

Then

convert<int*, int> c;
c();

will report ambiguous class template instantiation in g++-4.5, g++-4.6, g++-4.7 and clang++-3.1(all with option -std=c++0x)

But if i replace the check in third case to

typename std::enable_if<(check<int>::value && check<T>::value), void>:type

Then clang++-3.1 works fine.

Is it compilers bug or by standard?

like image 272
changsheng Avatar asked Jul 16 '12 07:07

changsheng


1 Answers

A similar issues arose in this question

Because both the second and third partial specializations are a match for convert<int*, int>, the compiler will build two test function templates with the two partially specialized class templates supplied as arguments:

template <class F, class T> 
void fun2(convert<F*, T, typename std::enable_if<
    (check<F>::value && check<T>::value), void>::type>
);

template <class T> 
void fun3(convert<int*, T, typename std::enable_if<
    (check<T>::value), void>::type>
);

The compiler then checks whether one function template is more specialized than the other by cross-substituting a set of transformed-parameters of one function into the other, and check whether all the template arguments can be deduced. If this works both ways, then neither function is more specialized than the other and ambiguity ensues.

The problem here is that the std::enable_if< (check<F>::value && check<T>::value), void>::type> is a non-deduced context, that will not be evaluated during this argument deduction game. The compiler only checks if the general expressions have the same structural form (where anything in front of a :: delimiter is deduced), not if they have the same value (true_type in this case).

Only by adding the extra check<int>::value in the third partial specialization, does the third specialization become more specialized than the second. Another "fix" would be to manually put true_type into the Check parameter, however, the compiler does not do that for you during argument deduction.

UPDATE: In response to Johannes Schaub - litb: you're right, the code with the std::check<int> put into the std::enable_if doesn't compile on Ideone and MSVC++ 2010. What to make of it? According to 14.8.2.4 clause 11 [temp.deduct.partial]

In most cases, all template parameters must have values in order for deduction to succeed, but for partial ordering purposes a template parameter may remain without a value provided it is not used in the types being used for partial ordering. [ Note: A template parameter used in a non-deduced context is considered used. — end note ] [Example:

template <class T> T f(int); // #1
template <class T, class U> T f(U); // #2
void g() {
    f<int>(1); // calls #1
}

For the code by the OP, I interpret this that the unused parameter would be the std::enable_if expression. My guess would be that Clang 3.1 does some expression matching that Ideone and MSVC++ don't do. I don't understand if in the context of the above quote, this is required or not by the Standard: should only unused template parameters be ignored, or also unused template expressions? There are other parts in the Standard where phrases like "not requiring implementations to use heroic efforts" appear. Perhaps Clang is more heroic than MSVC++ or Ideone in this respect.

like image 98
TemplateRex Avatar answered Sep 20 '22 16:09

TemplateRex