web-dev-qa-db-fra.com

Pourquoi gcc et clang produisent-ils chacun une sortie différente pour ce programme? (opérateur de conversion vs constructeur)

programme:

#include <stdio.h>

struct bar_t {
    int value;
    template<typename T>
    bar_t (const T& t) : value { t } {}

    // edit: You can uncomment these if your compiler supports
    //       guaranteed copy elision (c++17). Either way, it 
    //       doesn't affect the output.

    // bar_t () = delete;
    // bar_t (bar_t&&) = delete;
    // bar_t (const bar_t&) = delete;
    // bar_t& operator = (bar_t&&) = delete;
    // bar_t& operator = (const bar_t&) = delete;
};

struct foo_t {
    operator int   () const { return 1; }
    operator bar_t () const { return 2; }
};

int main ()
{
    foo_t foo {};
    bar_t a { foo };
    bar_t b = static_cast<bar_t>(foo);

    printf("%d,%d\n", a.value, b.value);
}

sortie pour gcc 7/8:

2,2

sortie pour clang 4/5 (également pour gcc 6.3)

1,1

Il semble que ce qui suit se produit lors de la création des instances de bar_t:

Pour gcc, il calls foo_t::operator bar_t puis constructs bar_t with T = int.

Pour clang, il constructs bar_t with T = foo_t puis calls foo_t::operator int

Quel compilateur est correct ici? (ou peut-être qu'ils sont tous les deux corrects s'il s'agit d'une forme de comportement non défini)

34
verb_noun

Je crois que le résultat de clang est correct.

Dans les deux bar_t a { foo } Initialisation de liste directe et dans un static_cast entre les types définis par l'utilisateur, les constructeurs du type de destination sont pris en compte avant les opérateurs de conversion définis par l'utilisateur sur le type source (C++ 14 [dcl.init.list]/3 [expr.static.cast]/4). Si la résolution de surcharge trouve un constructeur approprié, il est utilisé.

Lorsque vous effectuez une résolution de surcharge, bar_t::bar_t<foo_t>(const foo_t&) est viable et correspondra mieux qu'une à toute instanciation de ce modèle, ce qui entraînera l'utilisation des opérateurs de conversion sur foo. Il sera également meilleur que tous les constructeurs déclarés par défaut car ils prennent autre chose que foo_t, Donc bar_t::bar_t<foo_t> Est utilisé.


Le code tel qu'il est actuellement écrit dépend de l'élision de copie garantie C++ 17; Si vous compilez sans l'élision de copie garantie de C++ 17 (par exemple -std=c++14), Alors clang rejette ce code en raison de l'initialisation de la copie dans bar_t b = static_cast<bar_t>(foo);.

18
bames53