6

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?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
changsheng
  • 101
  • 1
  • 6

2 Answers2

1

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.

Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
0

You have

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

and

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

When you use convert<int*, int> c;, the compiler can't chose which of the structs he needs to use, because they both fit.

Note that you use check<F>::value in the first template. That means even if you pass, for example, an int *, you will have check<int>::value, not check<int *>::value

SingerOfTheFall
  • 29,228
  • 8
  • 68
  • 105
  • 2
    Pretty sure the second should be more specialized, as it takes less template parameters, for one thing. – Puppy Jul 16 '12 at 07:47
  • @DeadMG The non-deduced context of `std::enable_if<...>::type` messes up the argument deduction during the determination if one specialization is more specialized than the other. – TemplateRex Jul 16 '12 at 12:10
  • @rhalbersma: There is no argument deduction and as such no non-deduced context here, what are you getting at? – Xeo Jul 16 '12 at 12:16
  • @Xeo Yes, there is: 14.5.5.2 [temp.class.order] says "For two class template partial specializations, the first is at least as specialized as the second if, given the following rewrite to two function templates, the first function template is at least as specialized as the second according to the ordering rules for function templates (14.5.6.2)" – TemplateRex Jul 16 '12 at 12:20
  • @Xeo The actual ordering of the function templates is described in 14.5.6.2 [temp.func.order] and subsequently 14.8.2.4 [temp.deduct.partial]. – TemplateRex Jul 16 '12 at 12:22
  • @Xeo No, there are many steps. First the matching of class template partial specializations to `convert` is described in 14.5.5.1 [temp.class.spec.match]. Because there are multiple matches, partial ordering has to be done. This is described by the sequence of rules quoted above. – TemplateRex Jul 16 '12 at 12:24