2017-09-18 2 views
3

Pourquoi tuple_size est-il un trait libre et non un membre/variable/typedefs dans la classe? Ce dernier a un risque beaucoup plus faible de provoquer des violations ODR.Pourquoi tuple_size est-il un trait et non un membre

Existe-t-il un cas d'utilisation concret où avoir un trait tuple_size est mieux que de définir cette ligne dans la classe? Y a-t-il un cas motivant semblable à celui de iterator_traits (https://stackoverflow.com/a/6742025/5501675) Le seul que je vois est le bénéfice qu'il apporte avec les types incomplets. Bien que pas tout à fait sûr de ce qu'un cas d'utilisation pour c'est

tuple_size pourrait facilement être fait une chose membre comme celui-ci

class Something { 
public: 
    static constexpr auto tuple_size = 3; 
}; 

Et puis la mise en œuvre libre du trait tuple_size peut être

template <typename T, EnableIfHasTupleSize<std::decay_t<T>>* = nullptr> 
struct tuple_size 
    : public std::integral_constant<std::size_t, 
            std::decay_t<T>::tuple_size> 
{}; 

Une telle implémentation apporterait à la fois les avantages d'avoir un tuple_size qui peut fonctionner avec des types incomplets si spécialisés ainsi que d'offrir la sécurité de la constante membre si nécessaire. Similaire à iterator_traits


Cet exemple m'a été donné par @T.C. récemment de la façon dont tuple_size peut facilement causer des violations ODR.

struct X { int a; }; 

template<> struct std::tuple_size<X>; 
template<size_t I> struct std::tuple_element<I, X> { using type = int; }; 
template<size_t I> int get(X) { return 1; } 


call_func_that_checks_value_of_tuple_size(X{}); 

template<> struct std::tuple_size<X> : std::integral_constant<size_t, 3> {}; 

call_func_that_checks_value_of_tuple_size(X{}); 

sont Compilateurs en mesure de prendre à ce sujet, si cela est réparti entre les unités de traduction, il est très difficile pour un compilateur pour attraper ce.

Répondre

2

std::tuple a été implémenté dans le rapport technique 1 avant d'entrer dans la norme.

C'était avant l'existence de C++ 11, donc pas de fonctions constexpr. Puisque std::tuple_size est une valeur constante, il était logique d'en faire un gabarit à la fois pour en faire une expression constante (il pourrait parfaitement s'agir d'un membre consapepr AFAIK sans problème).

Comme pour le non-membre, std::array prend également en charge std::tuple_size et toute structure pourrait le soutenir en théorie. Donc, je pense que ce sont les raisons potentielles de la décision, mais je ne sais pas les vraies décisions derrière cela, je l'ai juste raisonné.

2

Dans C++ 17 liaisons structurées nécessitent un type de support std::tuple_size<T>, ADL-activé get<std::size_t>, et std::tuple_element<std::size_t, T>.

Si un type prend en charge tous les 3, les travaux de liaison structurée. Cela vous permet de prendre un type fourni par une tierce partie, comme , et sans modifier leur code source ou les obliger à mettre à jour, utilisez-le de manière structurée dans votre code.

Si tuple_size était membre de tuple (et array et pair), nous aurions dû ajouter un trait tuple_size en C++ 17 d'avoir cette capacité de toute façon.