c++ description Pourquoi utiliser deux sizeofs pour vérifier si une classe est constructible par défaut, mais pas une?




générer javadoc eclipse (2)

J'ai utilisé le code de " Existe-t-il un moyen de tester si une classe C ++ a un constructeur par défaut (autre que les caractères de type fournis par le compilateur)? ".

Je l'ai légèrement modifié pour fonctionner avec tous mes cas de test:

template< class T >
class is_default_constructible {
    typedef int yes;
    typedef char no;


    // the second version does not work
#if 1
    template<int x, int y> class is_equal {};
    template<int x> class is_equal<x,x> { typedef void type; };

    template< class U >
    static yes sfinae( typename is_equal< sizeof U(), sizeof U() >::type * );
#else
    template<int x> class is_okay { typedef void type; };

    template< class U >
    static yes sfinae( typename is_okay< sizeof U() >::type * );
#endif

    template< class U >
    static no sfinae( ... );

public:
    enum { value = sizeof( sfinae<T>(0) ) == sizeof(yes) };
};

Pourquoi cela fonctionne-t-il correctement avec la version à deux modèles mais pas avec la version normale (set #if 0 )? Est-ce un bug de compilateur? J'utilise Visual Studio 2010.

J'ai utilisé les tests suivants:

BOOST_STATIC_ASSERT( is_default_constructible<int>::value );
BOOST_STATIC_ASSERT( is_default_constructible<bool>::value );
BOOST_STATIC_ASSERT( is_default_constructible<std::string>::value );
BOOST_STATIC_ASSERT( !is_default_constructible<int[100]>::value );

BOOST_STATIC_ASSERT( is_default_constructible<const std::string>::value );

struct NotDefaultConstructible {
    const int x;
    NotDefaultConstructible( int a ) : x(a) {}
};

BOOST_STATIC_ASSERT( !is_default_constructible<NotDefaultConstructible>::value );

struct DefaultConstructible {
    const int x;

    DefaultConstructible() : x(0) {}
};

BOOST_STATIC_ASSERT( is_default_constructible<DefaultConstructible>::value );

Je suis vraiment perdue ici:

  1. Deux tests échouent avec l'autre version: int[100] et NotDefaultConstructible . Tous les tests réussissent avec la version à deux arguments.
  2. Visual Studio 2010 ne prend pas en charge std::is_default_constructible . Cependant, ma question est de savoir pourquoi il y a une différence dans les deux implémentations et pourquoi l'une fonctionne et l'autre non.

Answer #1

(Ma réponse est grandement éclairée par la réponse précédente de DS.)

Tout d'abord, notez que vous avez la class is_okay { typedef void type; } class is_okay { typedef void type; } , c'est-à-dire, type est un membre privé de is_okay . Cela signifie que ce n'est pas visible en dehors de la classe et donc

template< class U >
static yes sfinae( typename is_equal< sizeof U(), sizeof U() >::type * );

ne réussira jamais. Cependant, SFINAE ne s'appliquait pas à cette situation en C ++ 98; ce n'est que lors de la résolution du DR 1170 que "la vérification de l'accès a commencé à être effectuée dans le cadre du processus de substitution". [1]

(Il est étonnant que Paolo Carlini ait écrit ce billet il y a 10 jours, donc votre timing avec cette question est impeccable.) Dans ce cas, d'après Carlini, GCC avant 4.8 n'a pas du tout vérifié l'accès pendant la SFINAE. pourquoi vous n'avez pas vu GCC se plaindre de la nature privée du type.Vous devriez utiliser un GCC top-of-tree de littéralement moins de deux semaines, afin de voir le bon comportement.)

Clang (top-of-tree) suit le mode DR dans -std=c++11 , mais donne l'erreur attendue dans son mode C ++ 03 par défaut (ie Clang ne suit pas le DR en mode C ++ 03) . C'est un peu étrange, mais peut-être qu'ils le font pour la compatibilité ascendante.

Mais de toute façon , vous ne voulez pas que le type soit privé en premier lieu. Ce que vous vouliez écrire est struct is_equal et struct is_okay .

Avec ce changement, Clang passe tous vos cas de test. GCC 4.6.1 passe aussi tous vos cas de test, sauf int[100] . GCC pense que int[100] va bien, alors que vous affirmez que ce n'est pas correct.

Mais un autre problème avec votre code est qu'il ne teste pas ce que vous pensez qu'il teste. La norme C ++, clause 8.5 # 10, dit très clairement: [2]

Un objet dont l'initialiseur est un ensemble de parenthèses vide, c'est-à-dire, () , doit être initialisé par une valeur.

Ainsi, lorsque vous écrivez sizeof U() , vous ne testez pas si U peut être initialisé par défaut ; vous testez si la valeur peut être initialisée!

...Ou es-tu? Au moins dans certains de mes cas de test, les messages d'erreur de GCC indiquaient que U() était interprété comme le nom d'un type - "fonction renvoyant U " - et c'est pourquoi int[100] se comportait différemment. Je ne vois pas comment ce comportement est valide, mais je ne comprends vraiment pas les subtilités syntaxiques ici.

Si vous voulez réellement tester l'initialisation par défaut , vous devriez utiliser quelque chose comme sizeof *new U partout où vous avez actuellement sizeof U() .

Par ailleurs, int[100] est initialisable par défaut, point. La norme est claire sur ce que cela signifie d'initialiser par défaut un type de tableau.

Enfin, je me demande si l'une des causes du comportement farfelu dans votre code est que vous essayez de passer un 0 sans motif (qui est de type int ) à une fonction dont l'ensemble des surcharges inclut une fonction prenant void * et une autre prenant ... . Je pourrais totalement comprendre si un compilateur a choisi le mauvais dans ce cas. Vous feriez mieux d'essayer de passer 0 à une fonction prenant int .

En mettant tout cela ensemble, voici une version de votre code qui fonctionne parfaitement pour moi (c'est-à-dire, aucune assertion-échecs) dans ToT Clang et GCC 4.6.1.

template< class T >
class is_default_constructible {
    typedef int yes;
    typedef char no;

    template<int x> struct is_okay { typedef int type; };

    template< class U >
    static yes sfinae( typename is_okay< sizeof (*new U) >::type );

    template< class U >
    static no sfinae( ... );

public:
    enum { value = sizeof( sfinae<T>(0) ) == sizeof(yes) };
};

#if __has_feature(cxx_static_assert)
#define BOOST_STATIC_ASSERT(x) static_assert(x, "or fail")
#else
#define dummy2(line) dummy ## line
#define dummy(line) dummy2(line)
#define BOOST_STATIC_ASSERT(x) int dummy(__COUNTER__)[(x) - 1]
#endif

#include <string>

BOOST_STATIC_ASSERT( !is_default_constructible<int()>::value );
BOOST_STATIC_ASSERT( is_default_constructible<bool>::value );
BOOST_STATIC_ASSERT( is_default_constructible<std::string>::value );
BOOST_STATIC_ASSERT( is_default_constructible<int[100]>::value );

BOOST_STATIC_ASSERT( is_default_constructible<const std::string>::value );

struct NotDefaultConstructible {
    const int x;
    NotDefaultConstructible( int a ) : x(a) {}
};

BOOST_STATIC_ASSERT( !is_default_constructible<NotDefaultConstructible>::value );

struct DefaultConstructible {
    const int x;

    DefaultConstructible() : x(0) {}
};

BOOST_STATIC_ASSERT( is_default_constructible<DefaultConstructible>::value );

Answer #2

Cela semble presque certainement un artefact (bogue) du compilateur, puisque g ++ se comporte (et échoue) différemment. Je peux seulement deviner pourquoi VS se comporte différemment, mais une supposition qui semble raisonnable est que cette classe:

template<int x> class is_okay { typedef void type; };

a la même définition quel que soit le paramètre template, donc peut-être que le compilateur saute une étape lors de l'analyse static sfinae( typename is_okay< sizeof U() >::type * ); et considère qu'il est bien défini sans regarder de près le paramètre de is_okay . Alors, il pense que tout est par défaut constructible.

Pourquoi ni VS ni g ++ ne sont gênés par is_okay::type étant privé, je ne sais pas. On dirait qu'ils devraient tous les deux être.

D'un autre côté, g ++ considère les deux versions comme équivalentes. Dans les deux, cependant, cela donne une erreur différente pour int[100] . Celui-ci est discutable quant à savoir s'il devrait être constructible par défaut. Vous semblez penser que ça ne devrait pas être. std::is_default_constructible g ++ 47 pense que c'est ça! Pour obtenir ce comportement (qui est probablement plus standard), vous pouvez remplacer T par le nom de typename boost::remove_all_extents<T>::type dans la ligne d' enum .







metaprogramming