Restrict variadic template arguments
Yes it is possible. First of all you need to decide if you want to accept only the type, or if you want to accept a implicitly convertible type. I use std::is_convertible
in the examples because it better mimics the behavior of non-templated parameters, e.g. a long long
parameter will accept an int
argument. If for whatever reason you need just that type to be accepted, replace std::is_convertible
with std:is_same
(you might need to add std::remove_reference
and std::remove_cv
).
Unfortunately, in C++
narrowing conversion e.g. (long long
to int
and even double
to int
) are implicit conversions. And while in a classical setup you can get warnings when those occur, you don't get that with std::is_convertible
. At least not at the call. You might get the warnings in the body of the function if you make such an assignment. But with a little trick we can get the error at the call site with templates too.
So without further ado here it goes:
The testing rig:
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
};
C++20 Concepts
Not here yet, but soon. Available in gcc trunk (March 2020). This is the most simple, clear, elegant and safe solution:
#include <concepts>
auto foo(std::convertible_to<X> auto ... args) {}
foo(x, x, y, d); // OK
foo(x, x, y, d, z); // error:
We get a very nice error. Especially the
constraints not satisfied
is sweet.
Dealing with narrowing:
I didn't find a concept in the library so we need to create one:
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
We make use of the very nice fold expression:
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
Unfortunately we get a less clear error:
template argument deduction/substitution failed: [...]
Narrowing
We can avoid narrowing, but we have to cook a trait is_convertible_no_narrowing
(maybe name it differently):
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
We create a conjunction helper:
please note that in C++17
there will be a std::conjunction
, but it will take std::integral_constant
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> {};
and now we can have our function:
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
just minor tweaks to the C++14 version:
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
Acceptable way to restrict variadic templates in constructors
Overload resolution doesn't consider accessibility; that check is done later.
What you are doing is not actually SFINAE; it is not affecting overload resolution, only making the instantiation of the template constructor ill-formed after it's selected by overload resolution.
We can see this by adding a non-template constructor that's not as good a match:
template< class T >
class myobject
{
/* ... */
public:
/* ... */
myobject( const char * ptr )
{
std::cout << "const char * constructor" << std::endl;
}
template< typename... Ts >
myobject( Ts... params )
: myobject( typename CheckVaradicArgs<Ts...>::type(), params... )
{
std::cout << "Ts constructor with " << sizeof...(params) << std::endl;
}
};
char * p = nullptr;
myobject<int> something(p);
If it were SFINAE, the template would be taken out of overload resolution, and the const char *
constructor would be selected. Instead, you get a compile error as the compiler tries to instantiate the variadic template (g++ does the same thing).
To SFINAE, just add an extra template parameter:
template< class T >
class myobject
{
template< typename... Ts >
using CheckVariadicArgs =
typename std::enable_if<
all_true< std::is_convertible<Ts, T>... >::value, int
>::type;
public:
myobject( const myobject& other )
{
std::cout << "Copy constructor" << std::endl;
}
template< typename... Ts, CheckVariadicArgs<Ts...>* = nullptr >
myobject( Ts... params )
{
std::cout << "Ts constructor with " << sizeof...(params) << std::endl;
}
};
Demo.
Limit the number of parameters in a variadic template parameter pack
To make the function not callable when there's too many arguments, you can constraint the function with sfinae. That way, if there's another overload that accepts more arguments, the compiler will be able to select the correct overload.
A simple std::enable_if
with the condition will suffice:
template <class ...Args, std::enable_if_t<(sizeof...(Args) <= 10)>* = nullptr>
void setRequestArguments(const Args&... args)
{
const std::vector<QGenericArgument> vec = {args... };
}
For the sake of readability, you can put the constraint in the trailing return type of your function:
template <class ...Args>
auto setRequestArguments(const Args&... args) -> std::enable_if_t<(sizeof...(args) <= 10)>
{
const std::vector<QGenericArgument> vec = {args... };
}
Here's an updated version for C++20 using requires
and terse template syntax:
auto setRequestArguments(const auto&... args) requires (sizeof...(args) <= 10) -> void {
const std::vector<QGenericArgument> vec = {args... };
}
C++ templates that accept only certain types
I suggest using Boost's static assert feature in concert with is_base_of
from the Boost Type Traits library:
template<typename T>
class ObservableList {
BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
...
};
In some other, simpler cases, you can simply forward-declare a global template, but only define (explicitly or partially specialise) it for the valid types:
template<typename T> class my_template; // Declare, but don't define
// int is a valid type
template<> class my_template<int> {
...
};
// All pointer types are valid
template<typename T> class my_template<T*> {
...
};
// All other types are invalid, and will cause linker error messages.
[Minor EDIT 6/12/2013: Using a declared-but-not-defined template will result in linker, not compiler, error messages.]
Variadic template partial specialization of a class to restrict type of template arguments
This works:
#include <iostream>
template <int i, typename T> struct Arg;
template <typename ...T>
class Foo;
template <int ...Is, typename ...Ts>
class Foo<Arg<Is, Ts>...>
{
public:
static constexpr unsigned int N = sizeof...(Is);
};
int main()
{
using type2 = Foo<Arg<1, int>, Arg<7, float>, Arg<1, int>>;
std::cout << type2::N << "\n";
}
Though it might or might not be easy or convenient to use the template arguments in that form, depending on what you want to do with them.
C++ variadic template - limit number of args
Yes, this is possible, without static_assert
. For example, let's assume we want our vector
class to only be assignable with the name number of arguments as the dimension of the vector, you can use:
template<std::size_t Dim>
struct vector {
template <typename X0, typename ...Xn>
typename std::enable_if<sizeof...(Xn) + 1 == Dim, void>::type
assign(X0 x0, Xn... xn) {}
};
This just uses std::enable_if
in combination with sizeof...
to enable or disable the specific assign function.
So the following will compile:
vector<3> x;
x.assign(1, 2, 3);
Live demo
but this won't:
vector<3> x;
x.assign(1, 2, 3, 4);
Live demo
with (for Clang):
main.cpp:14:7: error: no matching member function for call to 'assign'
x.assign(1, 2, 3, 4);
~~^~~~~~
and neither will this:
vector<3> x;
x.assign(1, 2);
Live demo
with a similar error message.
How to iterate over Variadic template types (not arguments)?
In your example, in C++17, you might do:
template<typename... Ts>
class Subscriber
{
Subscriber()
{
auto f = [](auto data){ /* do something with data*/ };
(PubSub.Subscribe<Ts>(f), ...);
}
}
In C++11/14, you might to use more verbose way, such as:
(C++14 currently with your generic lambda)
template<typename... Ts>
class Subscriber
{
Subscriber()
{
auto f = [](auto data){ /* do something with data*/ };
int dummy[] = {0, (PubSub.Subscribe<Ts>(f), void(), 0)...};
static_cast<void>(dummy); // Avoid warning for unused variable.
}
}
Related Topics
Was Not Declared in This Scope' Error
Does Throw Inside a Catch Ellipsis (...) Rethrow the Original Error in C++
What Happens If a Constructor Throws an Exception
Why Can't I Initialize a Reference in an Initializer List with Uniform Initialization
What Is the Ascii Value of Eof in C
Align Cout Format as Table's Columns
Does Std::Mutex Create a Fence
How to Implement No-Op MACro (Or Template) in C++
Convert Char Array to Single Int
Pointers to Virtual Member Functions. How Does It Work
Right Shift and Signed Integer
How Similar Are Boost.Filesystem and the C++ Standard Filesystem Library
Does C and C++ Guarantee the Ascii of [A-F] and [A-F] Characters
When to Use Functors Over Lambdas
C++ Boost Asio Simple Periodic Timer
How Does the Linker Handle Identical Template Instantiations Across Translation Units