Oui, c'est possible. Tout d'abord, vous devez décider si vous voulez accepter uniquement le type, ou si vous voulez accepter un type implicitement convertible. J'utilise std::is_convertible
dans les exemples parce qu'il imite mieux le comportement des paramètres non modélisés, par exemple a long long
acceptera un paramètre int
argument. Si, pour une raison quelconque, vous avez besoin que seul ce type soit accepté, remplacez std::is_convertible
con std:is_same
(vous devrez peut-être ajouter std::remove_reference
et std::remove_cv
).
Malheureusement, en C++
la conversion par rétrécissement, par exemple ( long long
a int
et même double
a int
) sont des conversions implicites. Et alors que dans une configuration classique, vous pouvez obtenir des avertissements lorsque ces conversions se produisent, vous n'obtenez pas cela avec std::is_convertible
. Du moins pas à l'appel. Vous pouvez obtenir les avertissements dans le corps de la fonction si vous faites une telle affectation. Mais avec une petite astuce, nous pouvons obtenir l'erreur à l'emplacement de l'appel avec les modèles aussi.
Alors, sans plus attendre, voici ce qui se passe :
Le banc d'essai :
struct X {};
struct Derived : X {};
struct Y { operator X() { return {}; }};
struct Z {};
foo_x : function that accepts X arguments
int main ()
{
int i{};
X x{};
Derived d{};
Y y{};
Z z{};
foo_x(x, x, y, d); // should work
foo_y(x, x, y, d, z); // should not work due to unrelated z
};
Concepts du C++20
Pas encore ici, mais bientôt. Disponible dans gcc trunk (mars 2020). C'est la solution la plus simple, claire, élégante et sûre :
#include <concepts>
auto foo(std::convertible_to<X> auto ... args) {}
foo(x, x, y, d); // OK
foo(x, x, y, d, z); // error:
Nous obtenons une très belle erreur. Surtout le
contraintes non satisfaites
c'est doux.
Faire face au rétrécissement :
Je n'ai pas trouvé de concept dans la bibliothèque, nous devons donc en créer un :
template <class From, class To>
concept ConvertibleNoNarrowing = std::convertible_to<From, To>
&& requires(void (*foo)(To), From f) {
foo({f});
};
auto foo_ni(ConvertibleNoNarrowing<int> auto ... args) {}
foo_ni(24, 12); // OK
foo_ni(24, (short)12); // OK
foo_ni(24, (long)12); // error
foo_ni(24, 12, 15.2); // error
C++17
Nous utilisons le très beau expression des plis :
template <class... Args,
class Enable = std::enable_if_t<(... && std::is_convertible_v<Args, X>)>>
auto foo_x(Args... args) {}
foo_x(x, x, y, d, z); // OK
foo_x(x, x, y, d, z, d); // error
Malheureusement, nous obtenons une erreur moins claire :
La déduction/substitution d'arguments de modèle a échoué : [...]
Rétrécissement
Nous pouvons éviter le rétrécissement, mais nous devons cuisiner un trait is_convertible_no_narrowing
(peut-être le nommer différemment) :
template <class From, class To>
struct is_convertible_no_narrowing_impl {
template <class F, class T,
class Enable = decltype(std::declval<T &>() = {std::declval<F>()})>
static auto test(F f, T t) -> std::true_type;
static auto test(...) -> std::false_type;
static constexpr bool value =
decltype(test(std::declval<From>(), std::declval<To>()))::value;
};
template <class From, class To>
struct is_convertible_no_narrowing
: std::integral_constant<
bool, is_convertible_no_narrowing_impl<From, To>::value> {};
C++14
Nous créons une aide à la conjonction :
veuillez noter qu'en <code>C++17</code> il y aura un <code>std::conjunction</code> mais il faudra <code>std::integral_constant</code> arguments
template <bool... B>
struct conjunction {};
template <bool Head, bool... Tail>
struct conjunction<Head, Tail...>
: std::integral_constant<bool, Head && conjunction<Tail...>::value>{};
template <bool B>
struct conjunction<B> : std::integral_constant<bool, B> {};
et maintenant nous pouvons avoir notre fonction :
template <class... Args,
class Enable = std::enable_if_t<
conjunction<std::is_convertible<Args, X>::value...>::value>>
auto foo_x(Args... args) {}
foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // Error
C++11
juste des ajustements mineurs à la version C++14 :
template <bool... B>
struct conjunction {};
template <bool Head, bool... Tail>
struct conjunction<Head, Tail...>
: std::integral_constant<bool, Head && conjunction<Tail...>::value>{};
template <bool B>
struct conjunction<B> : std::integral_constant<bool, B> {};
template <class... Args,
class Enable = typename std::enable_if<
conjunction<std::is_convertible<Args, X>::value...>::value>::type>
auto foo_x(Args... args) -> void {}
foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // Error
0 votes
Avec les paramètres de modèle non typés, vous pouvez utiliser directement le type. Par exemple
int... args
5 votes
std::initializer_list<X>
pourrait être ce que vous voulez comme interface.0 votes
@Jarod42 C'est aussi un moyen. C'est plutôt un moyen de contournement. Les deux ont des inconvénients. Par exemple, vous ne pouvez pas passer de
std::initializer_list
. Et vous devez utiliser le{p1, p2, p3}
syntaxe. Avec les modèles variadiques, la mise en œuvre est plus compliquée et vous pouvez facilement vous tromper. Ce ne sera plus le cas avec les concepts, car l'implémentation est très simple et propre avec les concepts.0 votes
@AndyG Non, vous ne pouvez pas. Un modèle d'expansion doit contenir au moins un pack de paramètres de modèle.
0 votes
@L.F. Il s'agit d'un pack de paramètres. Le site
template<>
qui l'entourait était implicite. este c'est ce que je voulais dire.0 votes
@AndyG le problème est que vous ne pouvez pas utiliser les valeurs d'exécution.
0 votes
@AndyG Ah, maintenant je vois. Bon, c'est vrai, mais l'utilisation est plus limitée.