1
votes

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!

1
just leave the template constructor and specialize it for T=char const*bobah
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.dyp
This is the perfect example of why C++11 added the nullptr keyword.Manu343726
@DyP: Suppose I have already another typedef TemplateClassTest<int> intType;Code Warrior
@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*) { /*..*/ }dyp

1 Answers

1
votes

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;
}