I want to accept up to a number of parameters (this number being defined in a template parameter) in a template class constructor. I can't use an initializer_list, since I can't assert its size at compile time, as far as I know.
What I tried
My first attempt was using a std::array as a parameter:
template<size_t s>
class foo {
int v[s];
public:
foo(std::array<int, s>) {/*...*/}
};
However, that forces me to initialize like this (even when the constructor is not explicit) :
foo<4> a{{1,2,3,4}} // Two brackets.
I would think there may be some template magic (variadic templates?), but I can't even figure out the proper syntax to use in the constructor. I can't call a constructor recursively... can I?
I tried looking for a definition of the constructor for std::array(since it doesn't allow for more parameters than the size of the array, just what I want), but all I could find is that it has implicit constructors. Is that the default constructors? If so, how does
std::array<int, 3> a = {1,2,3}
work?
Optional bonus: Why didn't the standard define a fixed size alternative to std::initializer_list? Something like std::static_initializer_list<T, N>. Are there any plans on supporting this kind of functionality in the future? Is it even needed?
You could create a variadic constructor and just assert that it was provided the right number of arguments:
template <size_t SZ>
struct Foo {
template <typename... Args>
Foo(Args... args) {
static_assert(sizeof...(Args) <= SZ, "Invalid number of arguments");
// ... stuff ...
}
};
So that:
Foo<3> f; // OK
Foo<3> f(1, 2, 3); // OK
Foo<3> f(1, 2, 3, 4, 5); // error
As an example to initialize an array, that could look like:
template <size_t SZ>
struct Foo {
template <typename... Args>
Foo(Args... args)
: v{{args...}}
{
static_assert(sizeof...(Args) <= SZ, "Invalid number of arguments");
}
std::array<int, SZ> v;
};
That constructs v correctly as you'd expect, though if you try to pass more than SZ args to Foo's constructor, you'd see the error on initializing v before the static_assert.
For a clearer static_assert error, you could delegate the top-level Foo to private constructors that take an extra integral_constant argument for whether or not they're valid constructors:
template <typename... Args>
Foo(Args... args)
: Foo(std::integral_constant<bool, sizeof...(Args) <= SZ>{},
args...)
{ }
private:
template <typename... Args>
Foo(std::true_type, Args... args)
: v{{args...}}
{ }
template <typename False, typename... Args>
Foo(False, Args... )
{
// False is only ever std::false_type
static_assert(False::value, "Invalid number of arguments!");
}
Related
I'm currently trying to make a vector class in C++.
The class should have a constructor which accepts as many arguments as there are dimensions in the vector. The dimensionality is defined by a template when instantiating the vector.
All augments should be able to be converted into the same data type from which the vector originates (defined in the template to).
This is what I have written so far (I used many snippets from other questions here):
// this section is to create an typedef struct all_same which checks if all elements are of the same type
template<typename ...T> // head template
struct all_same : std::false_type { };
template<> // accept if no elements are present
struct all_same<> : std::true_type { };
template<typename T> // accept if only one element is present
struct all_same<T> : std::true_type { };
template<typename T, typename ...Ts> // check if first and second value are the same type and recurse
struct all_same<T, T, Ts...> : all_same<T, Ts...> { };
template<typename T, size_t N>
class vec_abs {
public:
vec_abs(const vec_abs &vec); // construct from another vector
explicit vec_abs(const std::array<T, N> &arr); // construct from array
template<typename ...Ts> requires // only compile if:
all_same<typename std::decay<Ts>::type...>::type // all are the same type (this might be optional, no sure)
&& std::conjunction<std::is_convertible<Ts, T>...>::value && // all can be converted to T
(sizeof(Ts) == N) // there are N arguments
explicit vec_abs(const Ts&&... values); // construct from multiple arguments
private:
std::array<T, N> data;
};
The fist section of the code tests if all arguments are of the same type, taken from this question.
I'm quite unsure about this solution (mainly because it's not working :D) and would appreciate any suggestions on improvements.
Thank you for your help!
If you do not want all arguments to be of same type you can remove that part. Apart from that you have a typo here:
(sizeof(Ts) == N)
should be
(sizeof...(Ts) == N)
After fixing that, forwarding the parameters and adding definition of the constructor, your code seems to do what you want:
#include <type_traits>
#include <array>
#include <iostream>
template<typename T, size_t N>
struct vec_abs {
template<typename ...Ts> requires // only compile if:
std::conjunction<std::is_convertible<Ts, T>...>::value && // all can be converted to T
(sizeof...(Ts) == N) // there are N arguments
explicit vec_abs(Ts&&... values) : data({std::forward<Ts>(values)...}) {}
std::array<T, N> data;
};
int main() {
auto foo = vec_abs<int,3>(1,2,3);
foo = vec_abs<int,3>(1.0,2,3);
for (const auto& e : foo.data) std::cout << e;
auto bar = vec_abs<std::string,3>("foo",std::string{"bar"},"moo");
for (const auto& e : bar.data) std::cout << e;
// bar = vec_abs<std::string,3>(123,1.2); // error wrong number
//bar = vec_abs<std::string,3>(123,1.2,42); // error cannot be converted
}
Output::
123foobarmoo
In case you actually do want the constraint that all parameters are of same type...
As the other answer mentions, the recursion in your all_same can be avoided. The following works already in C++11. Unfortunately I don't find the original source anymore.
template <typename T,typename...Ts>
struct all_same {
static const bool value = std::is_same< all_same, all_same<Ts...,T>>::value;
};
Recursive templates are very expensive (in terms of compile time and compiler memory-use) so while they work, and historically were the only way to do some solutions, more modern techniques are now available that are more succinct and compile much faster.
You can replace your entire all_same with a simple constexpr function using a fold expression:
template <typename T, typename... Ts>
constexpr bool all_same() {
return (std::is_same_v<T, Ts> && ...);
}
static_assert( all_same<int, int, int>() );
However, given that they all must be convertible to T, why do you also want the ensure they are the exact same type? That seems needlessly restrictive. You could easily change the function above to "all_convertible_to" using std::is_convertible_v<From, To> instead.
I am making a small helper class that derives from std::array. The constructor does not inherit, obviously, and it is that which is responsible for brace-initialization; for example:
template<typename T, size_t size>
struct foo : std::array<T,size>
{
foo(int a, int b)
: std::array<T,size>{a,b}
{
//nothing goes here since constructor is just a dummy that
//forwards all arguments to std::array constructor
}
}
int main()
{
foo<int,2> myobj = {1,2}; //brace initialization calls custom constructor with inner elements as arguments
}
The amount of arguments has to match exactly, so I am leaning towards using something like a variadic function argument in the constructor (since I am not only going to be using 2 elements in the array every single time). Using this, how would I forward the variadic argument pack to the std::array constructor? I am open to other methods of brace initialization that allow forwarding to the std::array constructor.
Note: std::initializer_list requires runtime initialization, and i am looking for a compile time/constexpr compatible method. Thank you.
You can use a perfect-forwarding constructor:
template<class... U>
foo(U&&... u)
: std::array<T, size>{std::forward<U>(u)...}
{}
I don't think that inheriting from a standard container is a good idea.
Anyway...
You can use variadic templates, perfect forwarding and also SFINAE to impose that the number of arguments is exactly size.
You can also make constexpr the foo constructor so you can make constexpr foo objects.
By example
#include <array>
#include <type_traits>
template <typename T, std::size_t S>
struct foo : public std::array<T, S>
{
template <typename ... As,
typename std::enable_if<sizeof...(As) == S>::type * = nullptr>
constexpr foo (As && ... as)
: std::array<T, S>{ { std::forward<As>(as)... } }
{ }
};
int main ()
{
//constexpr foo<int, 2u> myobj1 = {1}; // compilation error
constexpr foo<int, 2u> myobj2 = {1, 2}; // compile
//constexpr foo<int, 2u> myobj3 = {1, 2, 3}; // compilation error
}
I have a variadic function zoo which takes N arguments, where N is known at compile time (it is a template parameter of the class containing the function).
template <int N>
struct B
{
template <typename... Args>
static void zoo(Args... args)
{
static_assert(size of...(args) == N, "");
// do something
}
};
I have another variadic function foo which takes M arguments, where M>N and is known at compile time (it is a template parameter of the class containing the function). I have a static index_array containing the indices of the arguments of foo I want to pass to zoo.
From the body of foo I want to call zoo passing a selected subset of the arguments of foo.
What is the best way to do this? Ideally achieving perfect inlining, i.e. so that everything is compiled into just one instruction with no function pointers indirections?
template<int...I>
struct indices
{
static constexpr int N = sizeof...(I);
};
template <int M, typename...X>
struct A
{
// here I am simplifying, in reality IS will be built at compile time based on X
typedef indices<0,2,3> IS;
template <typename... Args>
static void foo(Args... args)
{
static_assert(size of...(args) == M, "");
// do some magic to achieve the function call described in pseudo-code
// B<IS::N>::zoo(args(IS(0),IS(1),IS(2)))
// ideally this should be perfectly inlined to just have the call above
}
};
Please note the code above is a simplification of my problem, designed for the purpose of illustrating the question.
EDIT:
As asked below, I describe the use case:
I am playing with a template based library to drive micro-controller pins. A micro controller has several ports (accessible as bytes in memory) and each port has up to 8 pins (bits). Class A is a bundle of pins via the template argument X, where every pin is defined as Pin. Class B manipulates all pins on the same port. A::foo is a function to modify some of the pins, with arguments in the same order as the order with which the pins are specified in the X template argument pack. foo needs to group the arguments by ports and dispatch to the B classes which representing individual ports, where all arguments are fused and written to the controller in a single instruction.
You can create a helper to extract the nth_arg like this:
template <int I>
struct ignore
{
template <typename T>
ignore(T&&) // This constructor accepts anything
{
}
};
template <typename T>
struct nth_arg;
template <size_t... DropIndexes>
struct nth_arg<std::integer_sequence<size_t, DropIndexes...>>
{
template <typename Arg, typename... Rest>
static decltype(auto) get(ignore<DropIndexes>..., // ignore args 0...n-1
Arg&& arg,
Rest&&...) // also ignore the rest
{
return std::forward<Arg>(arg); // return nth arg
}
};
And then call
template <int... Is, typename... Args>
static void call_zoo(indices<Is...>, Args&&... args)
{
B<sizeof...(Is)>::zoo(nth_arg<std::make_index_sequence<Is>>::get(
std::forward<Args>(args)...)...);
}
template <int M>
struct A
{
typedef indices<0, 2, 3> IS;
template <typename... Args>
static void foo(Args... args)
{
static_assert(sizeof...(args) == M, "");
call_zoo(IS{}, std::forward<Args>(args)...);
}
};
If you're using C++11, you can easily roll your own integer_sequence.
Pack the arguments into a tuple of references, and then retrieve them with std::get and a pack expansion on the indices.
template<class Tuple, int... Is>
static void magic(Tuple&& args, indices<Is...>){
B<IS::N>::zoo(std::get<Is>(std::forward<Tuple>(args))...);
}
template <typename... Args>
static void foo(Args... args)
{
static_assert(sizeof...(args) == M, "");
magic(std::forward_as_tuple(args...), IS{});
}
(You may want to make foo take forwarding references.)
Is it possible to initialize all elements of std::tuple by the same argument, using the non-default constructors of the underlying types?
template <typename... TElements>
struct Container {
// I'd wish to be able to do something like this:
Container(Foo foo, Bar bar)
: tuple(foo, bar)
{}
std::tuple<TElements...> tuple;
};
The point is that I don't know the tuple size (it's templated by a variadic parameter), so I can't duplicate the arguments as many times as I need. The only thing I know is that all types in TElements have a constructor taking Foo and Bar as arguments and don't have a default constructor.
The clearest way is just to construct each element in the tuple constructor argument list:
template <typename... TElements>
struct Container {
Container(Foo foo, Bar bar)
: tuple(TElements{foo, bar}...)
{}
std::tuple<TElements...> tuple;
};
This will result in move (or copy) constructing each element of the tuple from its corresponding constructor parameter; if this is unacceptable you could use piecewise construction:
template <typename... TElements>
struct Container {
Container(Foo foo, Bar bar)
: tuple(std::piecewise_construct, (sizeof(TElements), std::tie(foo, bar))...)
{}
std::tuple<TElements...> tuple;
};
Unfortunately in this case we have to do some kind of gymnastics (here sizeof and a comma operator) to get the variadic list TElements mentioned and ignored.
with double parameter pack expansion you can (try to) construct each element of a given tuple class with all given parameters to a function:
template <class T> struct tuple_construct_t;
template <class... Ts> struct tuple_construct_t<std::tuple<Ts...>> {
template <class... Args>
static std::tuple<Ts...> make_tuple(Args&&... args) {
//this is the central part - the double pack expansion
return std::make_tuple(Ts{args...}...);
}
};
// a little free helper function...
template <class Tup, class... Args>
Tup construct_tuple(Args&&... args) {
return tuple_construct_t<Tup>::make_tuple(std::forward<Args>(args)...);
}
And then somewhere in the code:
typedef std::tuple<NoDefault1, NoDefault2> myTuple;
auto t = construct_tuple<myTuple>(Foo{}, Bar{});
full working example: Link
Edit:
Since #Rakvan deleted his answer, I'll preserve the second (correct) part of it:
template <class ... Ts, class ... Args>
std::tuple<Ts...> cartesian_make_tuple(Args && ... args)
{
return std::make_tuple(Ts{args...}...);
}
here is a working exaple
We want to do variadic expansion (to get just the right amount of parameters), but we have to put a ‘hint’ to tie the expansion to whichever pack it is we want to match:
template<typename Dummy, typename Value>
Value depends(Value&& value)
{ return std::forward<Value>(value); }
template<typename... Elements>
void example()
{
// naive attempt to construct all the elements from 0:
// std::tuple<Elements...> t { 0... };
// now expansion is tied to the Elements pack
std::tuple<Elements...> t { depends<Elements>(0)... };
// with two arguments:
std::tuple<Elements...> t { { depends<Elements>(0), depends<Elements>(1) }... };
}
I have a function which takes one parameter with a default value. Now I also want it to take a variable number of parameters and forward them to some other function. Function parameters with default value have to be last, so... can I put that parameter after the variadic pack and the compiler will detect whether I'm supplying it or not when calling the function?
(Assuming the pack doesn't contain the type of that one last parameter. If necessary, we can assume that, because that type is generally not supposed to be known to the user, otherwise it's considered as wrong usage of my interface anyway....)
template <class... Args>
void func (Args&&... args, SomeSpecialType num = fromNum(5))
{
}
No, packs must be last.
But you can fake it. You can detect what the last type in a pack is. If it is SomeSpecialType, you can run your func. If it isn't SomeSpecialType, you can recursively call yourself with your arguments forwarded and fromNum(5) appended.
If you want to be fancy, this check can be done at compile time (ie, a different overload) using SFINAE techniques. But that probably isn't worth the hassle, considering that the "run-time" check will be constant on a given overload, and hence will almost certainly be optimized out, and SFINAE shouldn't be used lightly.
This doesn't give you the signature you want, but it gives you the behavior you want. You'll have to explain the intended signature in comments.
Something like this, after you remove typos and the like:
// extract the last type in a pack. The last type in a pack with no elements is
// not a type:
template<typename... Ts>
struct last_type {};
template<typename T0>
struct last_type<T0> {
typedef T0 type;
};
template<typename T0, typename T1, typename... Ts>
struct last_type<T0, T1, Ts...>:last_type<T1, Ts...> {};
// using aliases, because typename spam sucks:
template<typename Ts...>
using LastType = typename last_type<Ts...>::type;
template<bool b, typename T=void>
using EnableIf = typename std::enable_if<b, T>::type;
template<typename T>
using Decay = typename std::decay<T>::type;
// the case where the last argument is SomeSpecialType:
template<
typename... Args,
typename=EnableIf<
std::is_same<
Decay<LastType<Args...>>,
SomeSpecialType
>::value
>
void func( Args&&... args ) {
// code
}
// the case where there is no SomeSpecialType last:
template<
typename... Args,
typename=EnableIf<
!std::is_same<
typename std::decay<LastType<Args...>>::type,
SomeSpecialType
>::value
>
void func( Args&&... args ) {
func( std::forward<Args>(args)..., std::move(static_cast<SomeSpecialType>(fromNum(5))) );
}
// the 0-arg case, because both of the above require that there be an actual
// last type:
void func() {
func( std::move(static_cast<SomeSpecialType>(fromNum(5))) );
}
or something much like that.
Another approach would be to pass variadic arguments through a tuple.
template <class... Args>
void func (std::tuple<Args...> t, SomeSpecialType num = fromNum(5))
{
// don't forget to move t when you use it for the last time
}
Pros : interface is much simpler, overloading and adding default valued arguments is quite easy.
Cons : caller has to manually wrap arguments in a std::make_tuple or std::forward_as_tuple call. Also, you'll probably have to resort to std::index_sequence tricks to implement the function.
Since C++17 there is way to work around this limitation, by using class template argument deduction and user-defined deduction guides.
This is espactialy useful for C++20 std::source_location.
Here is C++17 demo:
#include <iostream>
int defaultValueGenerator()
{
static int c = 0;
return ++c;
}
template <typename... Ts>
struct debug
{
debug(Ts&&... ts, int c = defaultValueGenerator())
{
std::cout << c << " : ";
((std::cout << std::forward<Ts>(ts) << " "), ...);
std::cout << std::endl;
}
};
template <typename... Ts>
debug(Ts&&...args) -> debug<Ts...>;
void test()
{
debug();
debug(9);
debug<>(9);
}
int main()
{
debug(5, 'A', 3.14f, "foo");
test();
debug("bar", 123, 2.72);
}
Live demo
Demo with source_location (should be available since C++20, but still for compilers it is experimental).
This is coming a bit late, but in C++17 you can do it with std::tuple and it would be quite nice overall. This is an expansion to #xavlours 's answer:
template <class... Args>
void func (std::tuple<Args&&...> t, SomeSpecialType num = fromNum(5))
{
// std::apply from C++17 allows you to iterate over the tuple with ease
// this just prints them one by one, you want to do other operations i presume
std::apply([](auto&&... args) {((std::cout << args << '\n'), ...);}, t);
}
Then, make a simple function to prepare them:
template<typename... Args>
std::tuple<Args&&...> MULTI_ARGS(Args&&... args) {
return std::tuple<Args&&...>(args...);
}
Now you can call the function like this:
func(MULTI_ARGS(str1, int1, str2, str3, int3)); // default parameter used
func(MULTI_ARGS(str1, int1, str2)); // default parameter used
func(MULTI_ARGS(str1, int1, str2, str3, int3, otherStuff), fromNum(10)); // custom value instead of default
Disclaimer: I came across this question as I was designing a logger and wanted to have a default parameter which contains std::source_location::current() and as far as I was able to find, this is the only way that ensures the caller's information is passed accurately. Making a function wrapper will change the source_location information to represent the wrapper instead of the original caller.