Explicit vs Implicit constructor : un bug dans g++ ?
Bonjour.
Ce post fait suite à une discussion sur SO et j'avoue que je ne comprends pas bien ce que dit le standard sur cette situation.
Voilà le code incriminé :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #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;
} |
Le LWS est ici : http://liveworkspace.org/code/cd423f...b843732d837abc
La ligne qui me pose problème est l'avant dernière (celle où j'ai marqué WHY ?!?!)
Petite explication :
Mon but est :
- d'autoriser la conversion implicite de n'importe quelle Base<T1> en Derived<T>
- de bloquer la conversion implicite de Derived<T1> en Derived<T>
- mais d'autoriser une conversion explicite de Derived<T1> en Derived<T>
Le problème est que à la ligne incriminée, g++ passe par le constructeur implicite, moins spécialisé que le constructeur explicite. D'après le standard a-t-il le droit ? Ou est-ce un bug ?
Dans le cas où tout est OK vis à vis du standard, quelle est la solution la plus simple (idéalement en ne modifiant que le design de Derived, sans toucher à Base, ni Other et sans rajouter de classe (une solution à base de enable_if par exemple)) ?
EDIT : je rajoute ce "workaround" que je viens de trouver à l'instant. Ca implique seulement l'ajout d'un "faux" constructeur dans Derived. Cela semble-t-il une bonne façon de procéder (+ est-ce que tous les compilateurs sont susceptibles de donner le résultat que j'attends) ?
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #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;
} |