constructeur constexpr non appelé en tant que constexpr pour la conversion de type implicite

Aug 25 2020

J'ai fait du code qui est capable de distribuer à une fonction basée sur le site d'appel fournissant une chaîne associée à une fonction donnée (via un tupledes pointeurs de fonction et un tableau parallèle). Au lieu d'accepter directement une chaîne, la fonction de répartition accepte un Callabletype, où a const char*est convertible en a Callable.

Le constructeur de Callableis constexpret recherche une fonction à partir de la note tupleavec une recherche récursive de base. J'ai vérifié que le constructeur est capable de fonctionner correctement et de créer un constexpr Callable(exemple inclus). Puisque la fonction de répartition reçoit les arguments à passer aux Callable's operator(), je connais la signature de fonction attendue des Callable' s operator()au moment où je la crée.

J'essaie d'effectuer deux vérifications au moment de la compilation, alors qu'elles peuvent être effectuées au moment de la compilation. Tout d'abord, je vérifie que la chaîne fournie existe dans le tableau de chaînes prédéfini, du tout. Deuxièmement, je vérifie que la signature de la fonction associée à cette chaîne correspond à la signature attendue tupledes pointeurs de fonction. Je crée des messages d'erreur "amicaux" au moment de la compilation en throw()utilisant la constexprméthode qui recherche la fonction.

J'ai vérifié qu'en créant un constexprappelable, j'obtiens les messages d'erreur attendus au moment de la compilation. Cela marche. Ce qui ne fonctionne pas, c'est obtenir des messages au moment de la compilation si j'utilise Dispatcherdirectement my , laissant le site d'appel convertir une chaîne en un fichier Callable. Je sais que lorsque j'utilise des paramètres d'exécution, ma fonction de répartition ne sera pas appelée dans un constexprcontexte - je n'ai intentionnellement pas créé cette fonction constexpr; le but est de l'appeler avec des valeurs d'exécution. Mais je pensais que les conversions implicites "se produisaient sur le site d'appel" , pas dans la fonction appelée.

Par conséquent, j'ai pensé que dans un appel comme dispatcher("one", 1)(qui appelle la première fonction avec un paramètre de 1) ressemblerait à: "un" est converti en un Callable sur le site d'appel , puis un appel est effectué comme dispatcher(Callable("one"), 1). Cela signifierait que le constexprconstructeur pourrait au moins être utilisé. D'après mon expérience, tant que vous n'ignorez pas le résultat d'un constexprappel, l'appel est effectué comme constexprs'il pouvait l'être, sinon il est effectué en tant que runtime. Voir Fonctions Constexpr non appelées au moment de la compilation si le résultat est ignoré . Cela ne se produit pas - le constructeur de conversion est appelé au moment de l'exécution lorsque la conversion se produit dans un appel à ma fonction de répartition!

Est-ce que quelqu'un connaît un moyen de changer mon code pour que le constructeur de conversion soit appelé au moment de la compilation si cela peut l'être ??? J'ai trouvé une solution totalement différente pour résoudre cette classe générale de problème dans cet article , mais franchement, j'aime mieux la syntaxe du code ci-dessous, si je pouvais la faire fonctionner.

Je ne vais pas inclure le code ci-dessus dans le corps de cet article, mais plutôt inclure un exemple plus canonique qui démontre le comportement et montre également le comportement que j'ai vu dans l'article que j'ai référencé ci-dessus, tout-en-un.

Démo en direct de ce qui suit: https://onlinegdb.com/r1s1OE77v

Démo en direct de mon "vrai" problème, si vous êtes intéressé: https://onlinegdb.com/rJCQ2bGXw

D'abord les "bancs d'essai":

// Modified from https://stackoverflow.com/a/40410624/12854372

// In a constexpr context, ContextIsConstexpr1(size_t) always
// simply sets _s to 1 successfully.

extern bool no_symbol_s_is_zero;

struct ContextIsConstexpr1 {
    size_t _s;

    constexpr ContextIsConstexpr1(size_t s) : _s(s ? 1 : no_symbol_s_is_zero) {}
};

// In a constexpr context, ContextIsConstexpr2(size_t) will cause
// a compile-time error if 0 is passed to the constructor

struct ContextIsConstexpr2 {
    size_t _s;

    constexpr ContextIsConstexpr2(size_t s) : _s(1) {
        if(!s) {
            throw logic_error("s is zero");
        }
    }
};

// Accept one of the above. By using a CONVERSION constructor
// and passing in a size_t parameter, it DOES make a difference.

ContextIsConstexpr1 foo(ContextIsConstexpr1 c) { return c; }
ContextIsConstexpr2 bar(ContextIsConstexpr2 c) { return c; }

Maintenant, le code de test:

int main()
{
    constexpr size_t CONST = 1;
    #define TEST_OBVIOUS_ONES false
    
    // ------------------------------------------------------------
    // Test 1: result is compile-time, param is compile-time
    // ------------------------------------------------------------

    #if TEST_OBVIOUS_ONES
    
    // Compile-time link error iif s==0 w/ any optimization (duh)
    constexpr auto test1_1 = ContextIsConstexpr1(CONST);
    cout << test1_1._s << endl;

    // Compile-time throw iif s==0 w/ any optimization (duh)
    constexpr auto test1_2 = ContextIsConstexpr2(CONST);
    cout << test1_2._s << endl;

    #endif

    // ------------------------------------------------------------
    // Test 2: result is runtime, param is compile-time
    // ------------------------------------------------------------

    // Compile-time link error iif s==0 w/ any optimization ***See below***
    auto test2_1 = ContextIsConstexpr1(CONST);
    cout << test2_1._s << endl;

    // Runtime throw iif s==0 w/ any optimization
    // NOTE: Throw behavior is different than extern symbol behavior!!
    auto test2_2 = ContextIsConstexpr2(CONST);
    cout << test2_2._s << endl;

    // ------------------------------------------------------------
    // Test 3: Implicit conversion
    // ------------------------------------------------------------

    // Compile-time link error if (1) s==0 w/ any optimization *OR* (2) s>0 w/ low optimization!!
    // Note: New s>0 error due to implicit conversion ***See above***
    auto test3_1 = foo(CONST);
    cout << test3_1._s << endl;

    // Runtime throw iif s==0 w/ any optimization
    auto test3_2 = bar(CONST);
    cout << test3_2._s << endl;

    // ------------------------------------------------------------
    // Test 4: result is ignored, param is compile-time
    // ------------------------------------------------------------

    // Compile-time link error w/ any 's' iif low optimization
    // Note: no error w/ s==0 with high optimization, new error w/ s>0 by ignoring result ***See above***
    ContextIsConstexpr1{CONST};

    // Runtime throw iif s==0 w/ any optimization
    ContextIsConstexpr2{CONST};

    // ------------------------------------------------------------
    // Get runtime input, can't optimize this for-sure
    // ------------------------------------------------------------

    #if TEST_OBVIOUS_ONES

    size_t runtime;
    cout << "Enter a value: ";
    cin >> runtime;

    // ------------------------------------------------------------
    // Test 5: result is runtime, param is runtime
    // ------------------------------------------------------------

    // Compile-time link error w/ any 's' w/ any optimization (duh)
    auto test5_1 = ContextIsConstexpr1(runtime);
    cout << test5_1._s << endl;

    // Runtime throw iif s==0 w/ any optimization (duh)
    auto test5_2 = ContextIsConstexpr2(runtime);
    cout << test5_2._s << endl;

    // ------------------------------------------------------------
    // Test 6: result is ignored, param is runtime
    // ------------------------------------------------------------

    // Compile-time link error w/ any 's' w/ any optimization (duh)
    ContextIsConstexpr1{runtime};

    // Runtime throw iif s==0 w/ any 's' w/ any optimization (duh)
    ContextIsConstexpr2{runtime};

    #endif
}

Réponses

Jarod42 Aug 25 2020 at 14:44

Est-ce que quelqu'un connaît un moyen de changer mon code pour que le constructeur de conversion soit appelé au moment de la compilation si cela peut être

Comme je l'ai dit dans Linked Posté, l'appel de constexprfonctions au moment de la compilation se fait uniquement en expression constante.

Les paramètres ne sont pas constexpr.

Une solution de contournement serait d'utiliser MACRO:

#define APPLY_DISPATCHER(dispatcher, str, ...) \
    do { \
        constexpr callable_type_t<decltype(dispatcher),  decltype(make_tuple(__VA_ARGS__))> callable(str); \
        (dispatcher)(callable, __VA_ARGS__); \
    } while (0)

avec

template <typename Dispatcher, typename Tuple> struct callable_type;

template <typename Dispatcher, typename ... Ts>
struct callable_type<Dispatcher, std::tuple<Ts...>>
{
    using type = typename Dispatcher::template Callable<Ts...>;
};

template <typename Dispatcher, typename Tuple> 
using callable_type_t = typename callable_type<Dispatcher, Tuple>::type;

Avec l'utilisation:

APPLY_DISPATCHER(dispatcher, "one", 1);
APPLY_DISPATCHER(dispatcher, "a", 1); // Fail at compile time as expected

Démo .

Mais pas vraiment mieux que proposé dispatcher.dispatch(MAKE_CHAR_SEQ("a"), 1);(ou avec extension dispatcher.dispatch("a"_cs, 1);) (prévoyant une surcharge de répartition pour pouvoir créer constexpr Callable).