Considérons ce modèle de fonction:
template <class... T>
void foo (std::Tuple<T, char, double> ... x);
Cette invocation fonctionne:
using K = std::Tuple<int, char, double>;
foo ( K{1,'2',3.0}, K{4,'5',6.0}, K{7,'8',9.0} );
Celui-ci ne:
foo ( {1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0} );
(gcc et clang se plaignent tous deux de trop d'arguments pour foo
)
Pourquoi le deuxième appel pose-t-il un problème? Puis-je réécrire la déclaration de foo
afin que le deuxième appel soit également accepté?
Le paramètre de modèle T n'est utilisé que pour implémenter la variadicité. Le type actuel est connu et corrigé, seul le nombre d'arguments varie. Dans la vie réelle, les types diffèrent de int, char, double
, ce n'est qu'un exemple.
Je ne peux pas utiliser C++ 17 pour cela. Une solution compatible C++ 11 est de loin préférable.
Générez un ensemble surchargé de constructeurs:
#include <Tuple>
#include <cstddef>
template <typename T, std::size_t M>
using indexed = T;
template <typename T, std::size_t M, std::size_t... Is>
struct initializer : initializer<T, M, sizeof...(Is) + 1, Is...>
{
using initializer<T, M, sizeof...(Is) + 1, Is...>::initializer;
initializer(indexed<T, Is>... ts)
{
// ts is a pack of std::Tuple<int, char, double>
}
};
template <typename T, std::size_t M, std::size_t... Is>
struct initializer<T, M, M, Is...> {};
using foo = initializer<std::Tuple<int, char, double>, 20>;
// tuples limit+1 ~~~^
int main()
{
foo({1,'2',3.0});
foo({1,'2',3.0}, {4,'5',6.0});
foo({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});
}
Générez un ensemble surchargé d'opérateurs d'appel de fonction:
#include <Tuple>
#include <cstddef>
template <typename T, std::size_t M>
using indexed = T;
template <typename T, std::size_t M, std::size_t... Is>
struct initializer : initializer<T, M, sizeof...(Is) + 1, Is...>
{
using initializer<T, M, sizeof...(Is) + 1, Is...>::operator();
int operator()(indexed<T, Is>... ts) const
{
// ts is a pack of std::Tuple<int, char, double>
return 1;
}
};
template <typename T, std::size_t M, std::size_t... Is>
struct initializer<T, M, M, Is...>
{
int operator()() const { return 0; }
};
static constexpr initializer<std::Tuple<int, char, double>, 20> foo = {};
// tuples limit+1 ~~~^
int main()
{
foo({1,'2',3.0});
foo({1,'2',3.0}, {4,'5',6.0});
foo({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});
}
Créez (ou générez avec des macros de préprocesseur) un ensemble de surcharges qui transmettent les arguments à une seule implémentation:
#include <array>
#include <Tuple>
using K = std::Tuple<int, char, double>;
void foo(const std::array<K*, 5>& a)
{
// a is an array of at most 5 non-null std::Tuple<int, char, double>*
}
void foo(K p0) { foo({&p0}); }
void foo(K p0, K p1) { foo({&p0, &p1}); }
void foo(K p0, K p1, K p2) { foo({&p0, &p1, &p2}); }
void foo(K p0, K p1, K p2, K p3) { foo({&p0, &p1, &p2, &p3}); }
void foo(K p0, K p1, K p2, K p3, K p4) { foo({&p0, &p1, &p2, &p3, &p4}); }
int main()
{
foo({1,'2',3.0});
foo({1,'2',3.0}, {4,'5',6.0});
foo({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});
}
Passer comme un tableau et en déduire sa taille (nécessite une paire supplémentaire de parens):
#include <Tuple>
#include <cstddef>
template <std::size_t N>
void foo(const std::Tuple<int, char, double> (&a)[N])
{
// a is an array of exactly N std::Tuple<int, char, double>
}
int main()
{
foo({{1,'2',3.0}, {4,'5',6.0}});
// ^~~~~~ extra parens ~~~~~^
}
Utilisez un std::initializer_list
en tant que paramètre constructeur (pour ignorer les parenthèses supplémentaires):
#include <Tuple>
#include <initializer_list>
struct foo
{
foo(std::initializer_list<std::Tuple<int, char, double>> li)
{
// li is an initializer list of std::Tuple<int, char, double>
}
};
int main()
{
foo{ {1,'2',3.0}, {4,'5',6.0} };
}
{}
n'est pas une expression et n'a donc pas de type, la déduction d'argument concerne les types, une attention particulière est prise lorsque l'argument utilisé pour effectuer la déduction d'argument est une liste initializer, le paramètre de fonction template doit avoir des formes spécifiques, sinon paramètre est un contexte non déduit. Voici un exemple plus simpliste:
template <class T> struct A { T r; };
template <class T>
void foo (A<T> x);
using K = A<int>;
foo({1}); // fail
foo(K{1}); // compile
Ceci est couvert par [temp.deduc.call]/1
Si la suppression des références et des qualificatifs cv de P donne
std::initializer_list<P'>
ouP'[N]
pour certainsP'
etN
et que l'argument est une liste d'initialisation non vide ([dcl.init.list]), la déduction est effectuée à la place pour chaque élément de la liste d'initialisation. , en prenantP'
comme type de paramètre de modèle de fonction et l'élément initializer comme argument, et dans le casP'[N]
, siN
est un paramètre de modèle non typé,N
est déduit de la longueur de la liste d'initialisation. Sinon, un argument de la liste d'initialisation fait en sorte que le paramètre soit considéré comme un contexte non déduit.
Les contextes non déduits sont:
(5.6) Paramètre de fonction pour lequel l'argument associé est une liste d'initialiseurs ([dcl.init.list]) mais pour lequel le paramètre n'a pas de type pour lequel la déduction d'une liste d'initialiseurs est spécifiée ([temp.deduct.call]). .
Lorsque vous:
K{1}
, cela fonctionne ... l'argument n'est plus une liste initializer, est une expression avec le type.Je ne peux pas utiliser C++ 17 pour cela. Une solution compatible C++ 11 est de loin préférable.
Avec C++ 11, c'est un peu plus compliqué (pas de std::index_sequence
, pas de std::make_index_sequence
) mais, si vous voulez conserver l'utilisation variadique des n-uplets ... c'est ... si vous voulez réellement quelque chose comme
foo (std::Tuple<int, char, double> ... ts)
et si vous acceptez d'appeler une méthode statique d'une structure de modèle, vous pouvez définir une structure de modèle qui hérite de manière récursive et, de manière récursive, définir une
func ();
func (K t0);
func (K t0, K t1);
func (K t0, K t1, K t2);
où K
est votre
using K = std::Tuple<int, char, double>;
Voici un exemple de compilation complète de C++ 11
#include <Tuple>
#include <iostream>
using K = std::Tuple<int, char, double>;
template <typename T, std::size_t>
struct getTypeStruct
{ using type = T; };
template <typename T, std::size_t N>
using getType = typename getTypeStruct<T, N>::type;
template <int ...>
struct iList;
template <std::size_t = 50u, std::size_t = 0u, typename = iList<>>
struct foo;
template <std::size_t Top, std::size_t N, int ... Is>
struct foo<Top, N, iList<Is...>> : public foo<Top, N+1u, iList<0, Is...>>
{
using foo<Top, N+1u, iList<0, Is...>>::func;
static void func (getType<K, Is> ... ts)
{ std::cout << sizeof...(ts) << std::endl; }
};
template <std::size_t Top, int ... Is>
struct foo<Top, Top, iList<Is...>>
{
// fake func, for recursion ground case
static void func ()
{ }
};
int main()
{
foo<>::func({1,'2',3.0}); // print 1
foo<>::func({1,'2',3.0}, {4,'5',6.0}); // print 2
foo<>::func({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0}); // print 3
}
Si vous pouvez utiliser C++ 14, vous pouvez utiliser std::make_index_sequence
et std::index_sequence
et le code devient un peu meilleur, à mon humble avis
#include <Tuple>
#include <iostream>
#include <type_traits>
using K = std::Tuple<int, char, double>;
template <std::size_t ... Is>
constexpr auto getIndexSequence (std::index_sequence<Is...> is)
-> decltype(is);
template <std::size_t N>
using IndSeqFrom = decltype(getIndexSequence(std::make_index_sequence<N>{}));
template <typename T, std::size_t>
struct getTypeStruct
{ using type = T; };
template <typename T, std::size_t N>
using getType = typename getTypeStruct<T, N>::type;
template <std::size_t N = 50, typename = IndSeqFrom<N>>
struct foo;
template <std::size_t N, std::size_t ... Is>
struct foo<N, std::index_sequence<Is...>> : public foo<N-1u>
{
using foo<N-1u>::func;
static void func (getType<K, Is> ... ts)
{ std::cout << sizeof...(ts) << std::endl; }
};
template <>
struct foo<0, std::index_sequence<>>
{
static void func ()
{ std::cout << "0" << std::endl; }
};
int main()
{
foo<>::func({1,'2',3.0}); // print 1
foo<>::func({1,'2',3.0}, {4,'5',6.0}); // print 2
foo<>::func({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0}); // print 3
}
Dommage que vous ne puissiez pas utiliser C++ 17 car vous pouvez utiliser la variable unsing
et éviter tout héritage récursif.
#include <Tuple>
#include <iostream>
#include <type_traits>
using K = std::Tuple<int, char, double>;
template <std::size_t ... Is>
constexpr auto getIndexSequence (std::index_sequence<Is...> is)
-> decltype(is);
template <std::size_t N>
using IndSeqFrom = decltype(getIndexSequence(std::make_index_sequence<N>{}));
template <typename T, std::size_t>
struct getTypeStruct
{ using type = T; };
template <typename T, std::size_t N>
using getType = typename getTypeStruct<T, N>::type;
template <std::size_t N, typename = IndSeqFrom<N>>
struct bar;
template <std::size_t N, std::size_t ... Is>
struct bar<N, std::index_sequence<Is...>>
{
static void func (getType<K, Is> ... ts)
{ std::cout << sizeof...(ts) << std::endl; }
};
template <std::size_t N = 50, typename = IndSeqFrom<N>>
struct foo;
template <std::size_t N, std::size_t ... Is>
struct foo<N, std::index_sequence<Is...>> : public bar<Is>...
{ using bar<Is>::func...; };
int main()
{
foo<>::func({1,'2',3.0}); // print 1
foo<>::func({1,'2',3.0}, {4,'5',6.0}); // print 2
foo<>::func({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0}); // print 3
}