Question

The following code sum up my problem :

template<class Parameter>
class Base {};

template<class Parameter1, class Parameter2, class Parameter>
class Derived1 : public Base<Parameter>
{ };

template<class Parameter1, class Parameter2, class Parameter>
class Derived2 : public Base<Parameter>
{
public :
    // Copy constructor
    Derived2(const Derived2& x);

    // An EXPLICIT constructor that does a special conversion for a Derived2
    // with other template parameters
    template<class OtherParameter1, class OtherParameter2, class OtherParameter>
    explicit Derived2(
        const Derived2<OtherParameter1, OtherParameter2, OtherParameter>& x
    );

    // Now the problem : I want an IMPLICIT constructor that will work for every
    // type derived from Base EXCEPT
    // Derived2<OtherParameter1, OtherParameter2, OtherParameter> 
    template<class Type, class = typename std::enable_if</* SOMETHING */>::type>
    Derived2(const Type& x);
};

How to restrict an implicit constructor to all classes derived from the parent class excepted the current class whatever its template parameters, considering that I already have an explicit constructor as in the example code ?

EDIT : For the implicit constructor from Base, I can obviously write :

template<class OtherParameter> Derived2(const Base<OtherParameter>& x);

But in that case, do I have the guaranty that the compiler will not use this constructor as an implicit constructor for Derived2<OtherParameter1, OtherParameter2, OtherParameter> ?

EDIT2: Here I have a test : (LWS here : http://liveworkspace.org/code/cd423fb44fb4c97bc3b843732d837abc)

#include <iostream>
template<typename Type> class Base {};
template<typename Type> class Other : public Base<Type> {};
template<typename Type> class Derived : public Base<Type>
{
    public:
        Derived() {std::cout<<"empty"<<std::endl;}
        Derived(const Derived<Type>& x) {std::cout<<"copy"<<std::endl;}
        template<typename OtherType> explicit Derived(const Derived<OtherType>& x) {std::cout<<"explicit"<<std::endl;}
        template<typename OtherType> Derived(const Base<OtherType>& x) {std::cout<<"implicit"<<std::endl;}
};
int main()
{
    Other<int> other0;
    Other<double> other1;
    std::cout<<"1 = ";
    Derived<int> dint1;                     // <- empty
    std::cout<<"2 = ";
    Derived<int> dint2;                     // <- empty
    std::cout<<"3 = ";
    Derived<double> ddouble;                // <- empty
    std::cout<<"4 = ";
    Derived<double> ddouble1(ddouble);      // <- copy
    std::cout<<"5 = ";
    Derived<double> ddouble2(dint1);        // <- explicit
    std::cout<<"6 = ";
    ddouble = other0;                       // <- implicit
    std::cout<<"7 = ";
    ddouble = other1;                       // <- implicit
    std::cout<<"8 = ";
    ddouble = ddouble2;                     // <- nothing (normal : default assignment)
    std::cout<<"\n9 = ";
    ddouble = Derived<double>(dint1);       // <- explicit
    std::cout<<"10 = ";
    ddouble = dint2;                        // <- implicit : WHY ?!?!
    return 0;
}

The last line worry me. Is it ok with the C++ standard ? Is it a bug of g++ ?

Was it helpful?

Solution

Since each of the constructors you are referencing are templated class methods, the rules of template instantiation and function overload resolution are invoked.

If you look in section 14.8.3 of the C++11 standard, there are actually some examples in paragraphs 1-3 that somewhat demonstrate your question. Basically put, the C++ compiler will look for the best-match or "least generalized" template function instantiation among a series of overloaded template functions (with conversions of types added if necessary). In your case, because you have explicitly created a constructor that takes an alternate instantiation of a Derived2 object, that constructor will be a preferred overload for any Derived2<...> type compared to one that takes either a generic type T, or even a Base<OtherParameter> argument.

UPDATE: Apparently, according to 12.3.1/2 in the C++11 standard,

An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used.

The implications are that if you do not use the direct-initialization syntax for constructing your objects or opt for a cast, then you cannot use any constructors that are marked as explicit. This explains the puzzling results you're seeing between test #9 and #10.

OTHER TIPS

You could write a trait that reports whether or not a type is a specialization of Derived2<>:

template<typename T>
struct is_derived2 : std::false_type { };

template<class P1, class P2, class P>
struct is_derived2<Derived2<P1, P2, P>> : std::true_type { };

And a function stub to extract the P in Base<P>:

template<typename Parameter>
Parameter base_parameter(Base<Parameter> const&);

Then change your implicit constructor to:

template<
    class T,
    class = typename std::enable_if<
        !is_derived2<T>::value
        && std::is_base_of<
            Base<decltype(base_parameter(std::declval<T>()))>,
            T
        >::value
    >::type
>
Derived2(const T& x);

Online demo: http://liveworkspace.org/code/c43d656d60f85b8b9d55d8e3c4812e2b


Update: Here is an online demo incorporating these changes into your 'Edit 2' link:
http://liveworkspace.org/code/3decc7e0658cfd182e2f56f7b6cafe61

Ok, maybe I've found a workaround that only imply the addition of a "fake" constructor :

#include <iostream>
#include <type_traits>
template<typename Type> class Base {};
template<typename Type> class Other : public Base<Type> {};
template<typename Type> class Derived : public Base<Type>
{
    public:
        Derived() {std::cout<<"empty"<<std::endl;}
        Derived(const Derived<Type>& x) {std::cout<<"copy"<<std::endl;}
        template<typename OtherType> explicit Derived(const Derived<OtherType>& x) {std::cout<<"explicit"<<std::endl;}
        template<typename Something> Derived(const Something& x) {std::cout<<"implicit"<<std::endl;}

    // Workaround
    public:
        template<template<typename> class Something, typename OtherType,
        class = typename std::enable_if< std::is_same< Something<OtherType>, Derived<OtherType> >::value>::type >
        Derived(const Something<OtherType>& x)
        {std::cout<<"workaround (for example always false static assert here)"<<std::endl;}
};
template<unsigned int Size> class Test {};

int main()
{
    Other<int> other0;
    Other<double> other1;
    Test<3> test;
    std::cout<<"1 = ";
    Derived<int> dint1;                     // <- empty
    std::cout<<"2 = ";
    Derived<int> dint2;                     // <- empty
    std::cout<<"3 = ";
    Derived<double> ddouble;                // <- empty
    std::cout<<"4 = ";
    Derived<double> ddouble1(ddouble);      // <- copy
    std::cout<<"5 = ";
    Derived<double> ddouble2(dint1);        // <- explicit
    std::cout<<"6 = ";
    ddouble = other0;                       // <- implicit
    std::cout<<"7 = ";
    ddouble = other1;                       // <- implicit
    std::cout<<"8 = ";
    ddouble = ddouble2;                     // <- nothing (normal : default assignment)
    std::cout<<"\n9 = ";
    ddouble = Derived<double>(dint1);       // <- explicit
    std::cout<<"10 = ";
    ddouble = dint2;                        // <- workaround
    std::cout<<"11 = ";
    ddouble = test;                         // <- implicit
    return 0;
}

@Everybody : Do you think that it's a good solution to that problem ?

LWS : http://liveworkspace.org/code/f581356a7472c902b10ca486d648fafc

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top