Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit constructor available for all types derived from Base excepted the current type?

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++ ?

like image 441
Vincent Avatar asked Feb 20 '23 08:02

Vincent


2 Answers

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.

like image 177
Jason Avatar answered Apr 06 '23 10:04

Jason


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

like image 22
ildjarn Avatar answered Apr 06 '23 11:04

ildjarn