static_assert each parameter's size in parameter pack - c++

I am trying to check whether each parameter within a parameter pack can be stored within 8 bytes (sizeof <= 8)
I have the function signature:
template <typename Return, typename... Arguments>
inline auto invoke(std::uint64_t hash, Arguments... arguments) -> Return
Using fold expressions, I have tried:
static_assert((sizeof(arguments) <= 8 && ...));
Which failed to compile with unexpected token '...', expected 'expression' - I assume it's invalid or incorrect?
Using C++20 concepts and constraints, I assume something along the lines of is possible?
template <typename Return, typename... Arguments> requires (sizeof(arguments) <= 8 || ...)
inline auto invoke(std::uint64_t hash, Arguments... arguments) -> Return
I assume there is a way of using the standard library to say check that a type fits within a std::uint64_t say also?

With C++20 concepts, there are many ways how to achieve the desired behavior. For instance:
template <typename T, size_t N>
concept bool SizeLessEqual = sizeof(T) <= N;
template <SizeLessEqual<8>... Types>
void f() { }
int main() {
f<bool, char, int, double>();
f<std::string>(); // error
}
Live demo: https://wandbox.org/permlink/Q9tifNVplsx9BjGN
Another option is your solution:
template <typename... Types> requires ((sizeof(Types) <= 8) && ...)
void f() { }
Or, e.g.:
template <typename... Types> requires (std::max({ sizeof(Types)... }) <= 8)
void f() { }

Try this way:
#include <cstdint>
#include <utility>
template <typename... Arguments>
auto invoke(std::uint64_t hash, Arguments... arguments)
{
auto check = []( auto&& argument )
{
static_assert( sizeof(argument) <= 8, "size too large" );
return 0;
};
auto dummy = { 0, ( check(std::forward<Arguments>(arguments)), 0) ... };
return 0;
}
int main()
{
invoke( 0UL, '1' );
invoke( 0UL, '1', 2 );
invoke( 0UL, '1', 2, 3UL );
//invoke( 0UL, '1', 2, 3UL, static_cast<long double>(1.0) );
return 0;
}
Using the comma operator and the initializer_list to do the trick.
With C++17, we can further trim the code to:
template <typename... Arguments>
auto invoke(std::uint64_t hash, Arguments... arguments)
{
auto check = []( auto&& argument )
{
static_assert( sizeof(argument) <= 8, "size too large" );
};
(check(std::forward<Arguments>(arguments)), ...);
}
taking the advantage of fold expressions.
I do not understand the downvotes, but as this is my last post in stackoverflow, I uploaded a live example at wandbox: https://wandbox.org/permlink/NZbqpRaTs2TFOCwG

Related

Variadic concept constraints

Unfortunately this code doesn't work:
#include <iostream>
#include <type_traits>
#include <utility>
#include <tuple>
template<typename FirstArg, typename ... Args>
requires (sizeof ... (Args) == 0 || (std::is_convertible_v<Args ..., FirstArg>))
constexpr void multi_max( FirstArg firstArg, Args const &... args )
{
using namespace std;
FirstArg &max = firstArg;
auto findMax = [&]<size_t ... Is>( index_sequence<Is ...> iseq, tuple<Args ...> tArgs )
{
((max = get<Is>( tArgs ) > max ? get<Is>( tArgs ) : max), ...);
};
findMax( make_index_sequence<sizeof ... (Args)>(), make_tuple( args ... ) );
}
int main()
{
multi_max( 1 );
//multi_max( 1, 2, 3 );
}
The commented section doesn't fulfill the right half of the requires-constraint. Why ? And if I remove the first constraint the compilers complains about wrong unpacking of the args into a tuple.
Looking at the relevant portion of the compiler error:
note: because substituted constraint expression is ill-formed: too many template arguments for variable template 'is_convertible_v'
You're using is_convertible_v<int, int, int>. What you want is all tests of individual arguments to be true, which you can do with a fold expression:
requires (sizeof ... (Args) == 0 || (std::is_convertible_v<Args, FirstArg> && ...))
However, for a pack of 0 elements, the fold expression will produce true for &&, so the first part is now redundant:
requires (std::is_convertible_v<Args, FirstArg> && ...)
Cleaning up to use the concept convertible_to instead of the old type trait:
requires (std::convertible_to<Args, FirstArg> && ...)
Now things are looking a little suspiciously convenient. We can actually move this to the template parameter and get rid of the requires entirely:
template<typename FirstArg, std::convertible_to<FirstArg> ... Args>
This placeholder will shove FirstArg in as the second argument and place the given type as the first, so a given T would test std::convertible_to<T, FirstArg>.

how to specify C++ callable concept when the callable parameters types are deduced

I invoke a templated lambda from a templated function, the lambda parameters type are deduced. If the type of the lambda if auto, it works :
https://godbolt.org/z/WYxj5G8vx
#include <iostream>
#include <cstdint>
#include <array>
#include <functional>
#include <numeric>
#include <concepts>
template <typename T>
int testf2(T, auto fun) {
std::array<std::uint8_t, sizeof(T)> ar{};
std::iota(ar.begin(), ar.end(), 0);
return fun(ar);
}
int main() {
auto f2 = []<size_t S> (std::array<uint8_t, S> arr) -> int {
return arr[S -1];
};
std::cout << "R = " << testf2(5, f2) << std::endl;
}
I wanted to use std::invocable concept to specialize the auto fun parameter of testf2, to be anything but a callable that take std::array<std::uint8_t, N> as parameter.
Using gcc11.2 or clang13, when I try
template <typename T, size_t S>
int testf2(T, std::invocable<std::array<uint8_t, S>> auto fun) {
std::array<std::uint8_t, sizeof(T)> ar{};
std::iota(ar.begin(), ar.end(), 0);
return fun(ar);
}
I get error :
candidate template ignored: couldn't infer
template argument 'S' int testf2(T, std::invocable<std::array<uint8_t,
S>> auto fun) {
I don't understand why the compiler can infer type when only auto is used, but not with a constraining concept.
What is the correct way to use concept in this situation ?
This is a simplified version of the code, in reality the signature of testf2 is testf2(auto fun, ARGS... args) and the size of the array is calculated upon the parameter pack types.
============ EDIT 03/03/2022 ==================
Thanks for the correct answers, but I have oversimplified the code and the question, so I get right answer to a wrong question.
You need more context, I work with MCUs, and want to make a function that abstract some kind of spi,i2c,modbus, etc transaction where one send buffer to the slave peripheral and receive buffer in response. The function calculate write and read buffer length, serialise (doing endianness conversion if needed), call a lambda to do the actual transaction depending on the transport mechanism, deserialise and return. So the buffers lengths cannot be calculated with a (sizeof(Ts) + ...) as suggested.
I made a more realistic example :live example
// return empty array whose size is the sum of the two arrays given as parameters
template<typename T, std::size_t LL, std::size_t RL>
constexpr std::array<T, LL+RL> join(std::array<T, LL>, std::array<T, RL>)
{
return std::array<T, LL+RL>{};
}
// return an array of size sizeof(T) if T is arithmetic, otherwise an empty array
template <typename T>
constexpr auto count_ari(T) {
if constexpr (std::is_arithmetic_v<T>) {
return std::array<uint8_t, sizeof(T)>{};
} else {
return std::array<uint8_t, 0>{};
}
}
// return empty array whose size is the sum of all parameter which are arithmetic
template <typename HEAD, typename... TAIL>
constexpr auto count_ari(HEAD h, TAIL... tail) {
return join(count_ari(h), count_ari(tail...));
}
// create a iota filled array whose size is sum of all arithmetic parameters
// call a lambda given in parameter on this array
// return what has done the lambda
// it's here that I want to constrain parameter "auto fun"
template </*size_t S,*/ typename... ARGS>
int testf2(/*std::invocable<std::array<uint8_t, S>>, */ auto fun, ARGS... args) {
auto ar = count_ari(args...);
std::iota(ar.begin(), ar.end(), 1);
return fun(ar);
}
int main() {
auto f2 = []<size_t S> (std::array<uint8_t, S> arr) -> int {
return arr[S -1];
};
std::cout << "R = " << testf2(f2, 'a') << std::endl;
std::cout << "R = " << testf2(f2, 6, 7l, "foobar") << std::endl;
}
Question remains the same : is there a way to add constrain on the auto fun parameter of function testf2
Concepts (and requires clauses in general) do not participate in template argument deduction. Since your S in this case is just sizeof(T), you should use that.
the size S is the sum of all the sizes of the types of a parameter pack
Then make it sizeof(Args) + ....
You can use delcltype(count_ari(args...)) to get the resulting array type as the template parameter of std::invocable:
template <typename... ARGS>
int testf2(
std::invocable<decltype(count_ari(std::declval<ARGS>()...))> auto fun,
ARGS... args);
Or
template <typename... ARGS>
int testf2(auto fun, ARGS... args)
requires std::invocable<decltype(fun), decltype(count_ari(args...))>;
Demo
Nicol Bolas helps me to find the solution which was to make a constexpr function taking no argument that calculate the size, and specify the exact type of the callable with std::function instead of trying to specialise auto with invocable concept.
template <typename T>
constexpr size_t sizeof_ari() {
if constexpr (std::is_arithmetic_v<T>)
return sizeof(T);
else
return 0;
}
template <typename... ARGS>
constexpr size_t sizeof_aris() {
return (sizeof_ari<ARGS>() + ...);
}
// create a iota filled array whose size is sum of all arithmetic parameters
// call a lambda given in parameter on this array
// return what has done the lambda
template <typename... ARGS>
using lambda_param = std::array<uint8_t, sizeof_aris<ARGS...>()>;
template <typename... ARGS>
int testf2(std::function<int(lambda_param<ARGS...>)> fun, ARGS... args) {
auto ar = make_buf(args...);
std::iota(ar.begin(), ar.end(), 1);
return fun(ar);
}
demo

Function with a fixed amount of parameters determined by an integer

I have a class with a template that accepts an integer:
template <unsigned int N>
class Example {};
I'm looking for a way to define a (member)function that accepts some amount of Example objects as arguments. The amount is to be determined by N, so the function would be used like this:
Function(Example<2>(), Example<2>());
Function(Example<3>(), Example<3>(), Example<3>());
What I tried so far:
Using an initializer list, one is able to pass a set of objects to the function:
template <unsigned int N>
void Function(std::initializer_list<Example<N>> list);
//...
Function({Example<2>(), Example<2>()});
However, the problem besides the fact that really only one argument is passed(the list), is that with this method any number of arguments can be used:
Function({Example<2>()});
I also tried using a variadic function:
template <unsigned int N>
void Function(Example<N> e...)
{
va_list args;
va_start(args, e);
//...
}
Function(Example<2>(), Example<2>());
This makes it possible to use real parameters, but the problem of using any number of arguments remains, and it's not possible to know how many arguments were actually passed.
Assuming you want the number of arguments to be deduced from the Example<N> type, and that all Example<I> should share the same such N, a C++17 solution might be
template <unsigned int... I>
auto Function( Example<I>... ) ->
std::enable_if_t<( ( I == sizeof...(I) ) && ... )>
{
// or static_assert() if you always want an error
}
Make Function a variadic template and use std::enable_if_t to constrain your it:
Some IsExample trait can be used to make sure that all arguments are instances of Example
sizeof...(pack) can be used to get the size of the parameter pack
template <unsigned int N, typename... Ts>
auto Function(Ts... xs)
-> std::enable_if_t<(IsExample<Ts>::value && ...)
&& (sizeof...(Ts) == N)>
{
}
live example on wandbox
You should utilize variadic function template with static_assert. Unlike approaches involving enable_if this one will produce a readable error message if incorrect arguments are passed.
template<unsigned int ... I>
void Function(Example<I>... items)
{
static_assert
(
true && (... && (static_cast<unsigned int>(sizeof...(I)) == I))
, "This function accepts N arguments of type Example<N>"
);
}
Online compiler
There are many answers that cover SFINAE friendly based constraints, but I don't like placing my SFINAE in the return value:
template <unsigned int... Is,
std::enable_if_t<( ( Is == sizeof...(Is) ) && ... ), bool> = true
>
void Function( Example<Is>... examples )
{
// code
}
or
template<bool b>
using test_requirement = std::enable_if_t<b, bool>;
template <unsigned int... Is,
test_requirement<( ( Is == sizeof...(Is) ) && ... )> = true
>
void Function( Example<Is>... examples )
{
// code
}
+1 for the Massimiliano Janes's elegant solution.
Unfortunately use folding so works only for C++17.
To test, with C++11/C++14, that all I are equals to sizeof...(I) (and maybe that sizeof...(I) is equal to N, where N is the class template argument), it's enough test that a variadic type, that receive unsigned values, is the same type with a different order of values.
I mean: declaring a trivial struct as
template <std::size_t ... Is>
struct IList;
the test can be
std::is_same<IList<N, sizeof...(Is), Is...>,
IList<sizeof...(Is), Is..., N>>::value
Starting from C++14 it's possible to use std::index_sequence instead of IList.
So Example can be written as
template <unsigned int N>
struct Example
{
template <unsigned int ... Is>
auto Function (Example<Is> ...)
-> typename std::enable_if<
std::is_same<IList<N, sizeof...(Is), Is...>,
IList<sizeof...(Is), Is..., N>>::value>::type
{ /* do something */ }
};
The following is a example of use (but remember to include <type_traits>)
int main()
{
Example<1U> e1;
Example<2U> e2;
// e1.Function(); // error
e1.Function(Example<1>{}); // compile
//e1.Function(Example<1>{}, Example<1>{}); // error
// e2.Function(); // error
//e2.Function(Example<2>{}); // error
e2.Function(Example<2>{}, Example<2>{}); // compile
//e2.Function(Example<2>{}, Example<2>{}, Example<2>{}); // error
}

Template Type != Deduced type

I'm attempting to give a friendly name to a type as a template typename because I need to use the name in a few places within the function. The type is being deduced based on the number of other template arguments in the parameter pack, as shown below:
#include <cassert>
#include <functional>
#include <type_traits>
template < typename ... TArgs, typename Functor = std::function< std::conditional_t< sizeof...(TArgs) == 0, int (), int (TArgs...) > > >
void DoStuff(const Functor & func, TArgs ... args) {
if constexpr (sizeof...(TArgs) == 0)
assert(typeid(Functor).hash_code() == typeid(std::function<int ()>).hash_code());
else
assert(typeid(Functor).hash_code() == typeid(std::function<int (TArgs...)>).hash_code());
}
int main(int argc, char * argv[]) {
DoStuff([] () { return 5; });
DoStuff([] (int a) { return a; });
return 0;
}
This compiles just fine, but both assertions fail because the alias Functor is not actually a std::function<>. On the other hand, if I change the code to repeat the definition of Functor in the typeid calls it works perfectly, like below:
#include <cassert>
#include <functional>
#include <type_traits>
template < typename ... TArgs, typename Functor = std::function< std::conditional_t< sizeof...(TArgs) == 0, int (), int (TArgs...) > > >
void DoStuff(const Functor & func, TArgs ... args) {
if constexpr (sizeof...(TArgs) == 0)
assert(typeid(std::function< std::conditional_t< sizeof...(TArgs) == 0, int (), int (TArgs...) > >).hash_code() == typeid(std::function<int ()>).hash_code());
else
assert(typeid(std::function< std::conditional_t< sizeof...(TArgs) == 0, int (), int (TArgs...) > >).hash_code() == typeid(std::function<int (TArgs...)>).hash_code());
}
int main(int argc, char * argv[]) {
DoStuff([] () { return 5; });
DoStuff([] (int a) { return a; });
return 0;
}
Why is the first declaration (using the typname Functor = ...) incorrect? Is there a different way to make that alias? Note that in answering the second question, it is OK if the solution is a const expression, the examples are only not constexpr because of the use of typeid.
Why is the first declaration (using the typename Functor = ...) incorrect?
You're providing a default type for the template parameter Functor. But the default type is only used if the type is not otherwise specified or deduced. In this case, template deduction will deduce Functor in both cases to be whatever the unique type of the lambda is that it is invoked with (while Args... deduces as an empty pack).
This is similar to default function arguments not being used when they are provided.
I'm not sure what your assert()s are supposed to accomplish, but if you're checking types you should use static_assert.

Default initialized (with value initialization) parameter pack

Can I default initialize a parameter pack to the respective value initialization of each type ?
To elaborate a bit more, take the example of a simple function template
template<typename T>
void f(T arg = T())
{
// eg for T=int, arg is 0 (value initialization) when default initialized
}
Would it be possible to express its variadic counterpart, ie
template<typename... Args>
void F(Args... args /* how can I value initialize the parameter pack? */)
{
}
#include <iostream>
#include <utility>
#include <tuple>
#include <cstddef>
#include <type_traits>
template <typename... Args>
void F(Args... args)
{
// target function, arbitrary body
using expander = int[];
(void)expander{ 0, (void(std::cout << args << " "), 0)... };
std::cout << std::endl;
}
template <typename... Args, typename... Params, std::size_t... Is>
void F(std::index_sequence<Is...>, Params&&... params)
{
F<Args...>(std::forward<Params>(params)...
, std::decay_t<typename std::tuple_element<sizeof...(Params) + Is, std::tuple<Args...>>::type>{}...);
}
template <typename... Args, typename... Params>
auto F(Params&&... params)
-> std::enable_if_t<(sizeof...(Args) > sizeof...(Params))>
{
F<Args...>(std::make_index_sequence<sizeof...(Args) - sizeof...(Params)>{}
, std::forward<Params>(params)...);
}
Tests:
#include <string>
int main()
{
// F(int, char, float = float{}, double = double{})
F<int, char, float, double>(1, 'c');
// F(int = int{}, char = char{}, float = float{}, double = double{})
F<int, char, float, double>();
// F(const std::string&, const std::string& = std::string{})
F<const std::string&, const std::string&>("foo");
// F(int, int, int)
F(1, 2, 3);
}
Output:
1 'c' 0 0
0 '\0' 0 0
"foo" ""
1 2 3
DEMO
You can create two parameter packs, one representing the types corresponding to function parameters and one representing "defaulted parameters."
template< typename ... aux, typename ... arg >
void fn( arg ... a ) {
std::tuple< aux ... > more {}; // The tuple elements are value-initialized.
}
http://coliru.stacked-crooked.com/a/1baac4b877dce4eb
There is no way to explicitly mention the deduced template parameters for this function. Anything inside the angle braces of the call will go into aux, not arg.
Note, the initialization you get with {} is value-initialization, not default-initialization. Objects of fundamental type get zeroed, not left uninitialized.
It`s explicitly forbidden by C++ standard, you cannot do such thing.
N3376 8.3.6/3
A default argument shall be specified only in the
parameter-declaration-clause of a function declaration or in a
template-parameter (14.1); in the latter case, the initializer-clause
shall be an assignment-expression. A default argument shall not be
specified for a parameter pack.