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 : Sélectionner tout - Visualiser dans une fenêtre à part
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 : Sélectionner tout - Visualiser dans une fenêtre à part
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;
}