web-dev-qa-db-fra.com

Paramètre de modèle par défaut & lambda dans un contexte non évalué: bug ou fonctionnalité?

Nous considérons l'objectif de créer deux types différents, en utilisant exactement la même syntaxe. Cela peut être facilement fait avec des lambdas:

auto x = []{};
auto y = []{};
static_assert(!std::is_same_v<decltype(x), decltype(y)>);

Mais au lieu d'utiliser des lambdas, nous recherchons une autre syntaxe plus élégante. Voici quelques tests. Nous commençons par définir quelques outils:

#include <iostream>
#include <type_traits>
#define macro object<decltype([]{})>
#define singleton object<decltype([]{})>

constexpr auto function() noexcept
{
    return []{};
}

template <class T = decltype([]{})>
constexpr auto defaulted(T arg = {}) noexcept
{
    return arg;
}

template <class T = decltype([]{})>
struct object
{
    constexpr object() noexcept {}
};

template <class T>
struct ctad
{
    template <class... Args>
    constexpr ctad(const Args&...) noexcept {}
};

template <class... Args>
ctad(const Args&...) -> ctad<decltype([]{})>;

et les variables suivantes:

// Lambdas
constexpr auto x0 = []{};
constexpr auto y0 = []{};
constexpr bool ok0 = !std::is_same_v<decltype(x0), decltype(y0)>;

// Function
constexpr auto x1 = function();
constexpr auto y1 = function();
constexpr bool ok1 = !std::is_same_v<decltype(x1), decltype(y1)>;

// Defaulted
constexpr auto x2 = defaulted();
constexpr auto y2 = defaulted();
constexpr bool ok2 = !std::is_same_v<decltype(x2), decltype(y2)>;

// Object
constexpr auto x3 = object();
constexpr auto y3 = object();
constexpr bool ok3 = !std::is_same_v<decltype(x3), decltype(y3)>;

// Ctad
constexpr auto x4 = ctad();
constexpr auto y4 = ctad();
constexpr bool ok4 = !std::is_same_v<decltype(x4), decltype(y4)>;

// Macro
constexpr auto x5 = macro();
constexpr auto y5 = macro();
constexpr bool ok5 = !std::is_same_v<decltype(x5), decltype(y5)>;

// Singleton
constexpr singleton x6;
constexpr singleton y6;
constexpr bool ok6 = !std::is_same_v<decltype(x6), decltype(y6)>;

et le test suivant:

int main(int argc, char* argv[])
{
    // Assertions
    static_assert(ok0); // lambdas
    //static_assert(ok1); // function
    static_assert(ok2); // defaulted function
    static_assert(ok3); // defaulted class
    //static_assert(ok4); // CTAD
    static_assert(ok5); // macro
    static_assert(ok6); // singleton (macro also)

    // Display
    std::cout << ok1 << std::endl;
    std::cout << ok2 << std::endl;
    std::cout << ok3 << std::endl;
    std::cout << ok4 << std::endl;
    std::cout << ok5 << std::endl;
    std::cout << ok6 << std::endl;

    // Return
    return 0;
}

ceci est compilé avec la version actuelle du tronc de GCC, avec les options -std=c++2a. Voir le résultat ici dans l'explorateur du compilateur.


Le fait que ok0, ok5 et ok6 le travail n'est pas vraiment une surprise. Cependant, le fait que ok2 et ok3 sont true tandis que ok4 n'est pas très surprenant pour moi.

  • Quelqu'un pourrait-il expliquer les règles qui font que ok3true mais ok4false?
  • Est-ce vraiment ainsi que cela devrait fonctionner, ou s'agit-il d'un bogue du compilateur concernant une fonctionnalité expérimentale (lambdas dans des contextes non évalués)? (les références à la norme ou aux propositions C++ sont les bienvenues)

Remarque: j'espère vraiment qu'il s'agit d'une fonctionnalité et non d'un bug, mais simplement parce que cela rend certaines idées folles implémentables

39
Vincent

Quelqu'un pourrait-il expliquer les règles qui rendent ok3 vrai mais ok4 faux?

ok3 est vrai car utilise le type lambdas comme type par défaut.

Le type d'une expression lambda (qui est également le type de l'objet de fermeture) est un unique, non nommé non type de classe d'union,

Par conséquent, le type de modèle par défaut pour object, le type de paramètre de modèle pour macro et singltone toujours différent après chaque instatiation. Mais, pour la fonction function, l'appel retourné lambda est unique et son type est unique. La fonction de modèle ctad n'a de modèle que pour les paramètres mais la valeur de retour est unique. Si la réécriture fonctionne comme:

template <class... Args, class T =  decltype([]{})>
ctad(const Args&...) -> ctad<T>;

Dans ce cas, le type de retour sera différent après chaque instanciation.

2
Andrey Sv

pour le type de paramètre de fonction ok2 (T) dépend du paramètre de modèle spécifié. pour ok3 ctor n'est pas un modèle.

pour ok4, les deux déductions dépendent de la même liste de types de paramètres (qui est vide dans ce cas) et, en raison de cette déduction, ne se produit qu'une seule fois. l'instanciation et la déduction du modèle sont des choses différentes. alors que pour la même liste de types de paramètres, la déduction ne se produit qu'une seule fois, l'instanciation se produit pour toutes les utilisations.

regardez ce code ( https://godbolt.org/z/ph1Wk2 ). si les paramètres sont différents pour la déduction, des déductions distinctes se produisent.

0
mfurkanuslu