1

I have a simple issue with ctor overload resolution for a class template:

#include <iostream>
#include <string>

using namespace std;
enum EnumTypeVal { READ, WRITE };

template <class T>
class TemplateClassTest {
public:
    TemplateClassTest(const string & stringVal, T typeVal, EnumTypeVal e = READ,
                      const string & defaultStringVal = "default");
    TemplateClassTest(const string & stringVal, const char * charVal);
    TemplateClassTest(const string & stringVal, EnumTypeVal e = READ,
                      const string & defaultStringVal = "default");
private:
T type;
};

template <class T>
TemplateClassTest<T>::TemplateClassTest(const string & stringVal, T typeVal,
                                        EnumTypeVal e,
                                        const string & defaultStringVal)
{
    type = typeVal;
    cout << "In TemplateClassTest(const string &, T, EnumTypeVal, "
            "const string &)" << endl;
}

template <class T>
TemplateClassTest<T>::TemplateClassTest(const string & stringVal,
                                        const char * charVal)
{
    cout << "In TemplateClassTest(const string &, const char *)" << endl;
}

template <class T>
TemplateClassTest<T>::TemplateClassTest(const string & stringVal, EnumTypeVal e,
                                        const string & defaultStringVal)
{
    cout << "In TemplateClassTest(const string &, EnumTypeVal, const string &)"
         << endl;
}

typedef TemplateClassTest<long long unsigned int> u32Type;
typedef TemplateClassTest<bool> boolType;

int main()
{
    u32Type l("test", "0"); //matches ctor 2
    u32Type v("test", 0); // ambiguity between ctor 1 and 2
    boolType b("test", "true"); //matches ctor 2
    return 0;
}

The second call fails to compile by throwing error:

Call of overloaded 'TemplateClassTest(const char [5], int) is ambiguous.

Why does int matches to const char *? This situation can be solved by changing the const char * to const string & in ctor 2. But doing so, boolType b("test", "true") now gets matched to ctor 1 instead of ctor 2.

My requirements are:

  • u32Type v("test", 0) should match ctor 1
  • boolType b("test", "true") should match ctor 2.

Limitations are:

  • ctor 1 and 3 signatures can't be changed
  • user code calls in main() can't be changed.

Any help is highly appreciated..Thanks!

7
  • just leave the template constructor and specialize it for T=char const* Commented Nov 5, 2013 at 18:48
  • typedef TemplateClassTest<int> u32Type; would solve the problem, as ctor 1 becomes an Exact Match. I'm not sure if that wouldn't break something different, as int is not required to have exactly 32 bits. Commented Nov 5, 2013 at 19:02
  • 1
    This is the perfect example of why C++11 added the nullptr keyword. Commented Nov 5, 2013 at 19:08
  • @DyP: Suppose I have already another typedef TemplateClassTest<int> intType; Commented Nov 5, 2013 at 19:17
  • 1
    @CodeWarrior 1) the ctor is private. 2) An out-of-line definition of a member function of an explicitly specialized class template does not contain a template<> for the explicitly specialized class template. Just use TemplateClassTest<bool>::TemplateClassTest(const string&, const char*) { /*..*/ } Commented Nov 6, 2013 at 17:19

1 Answer 1

1

It is ambiguous because 0 is a null pointer constant. A null pointer constant can be implicitly converted to any pointer type -> a null pointer conversion. Therefore, ctor 2 is viable: TemplateClassTest(const string&, const char*).

As an integer literal, 0 is of type int. Constructor 1, TemplateClassTest(const string&, T, EnumTypeVal = READ, const string & = "default"), therefore requires a conversion from int to unsigned long long -> an integral conversion.

Those two conversions, null pointer conversion and integral conversion, have the same rank (Conversion), hence the ambiguity.


[conv.ptr]/1

A null pointer constant is an integral constant expression prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion.


A possible, but ugly fix that matches your constraints is to change the second constructor to:

template<class U,
         class V = typename std::enable_if<std::is_same<U, const char*>{}>::type>
TemplateClassTest(const string & stringVal, U charVal);

That is, a greedy constructor template, restricted by SFINAE to only accept const char* as the second argument. This heavily restricts the implicit conversions applied to the second argument when trying to match this ctor.

The out-of-line definition becomes:

template <class T>
template<class U, class V>
TemplateClassTest<T>::TemplateClassTest(const string & stringVal, U charVal)
{
    cout << "In TemplateClassTest(const string &, const char *)" << endl;
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the response..Why isn't it matching ctor 1 since it should have a better rank..since 0 is also type T in this case...How should I make u32Type v("test", 0) and boolType b("test", "true") to matche separate ctor's?
@CodeWarrior 0 as an integer literal is of type int, not of type unsigned long long int.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.