2

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

user3897188
  • 293
  • 2
  • 6
  • 1
    `std::invocable>`? – Jarod42 Feb 02 '22 at 18:01
  • Yes, I apologize, I have not explained in my initial post that in reality, the size S is the sum of all the sizes of the types of a parameter pack, but I have simplified the problem for the question. – user3897188 Feb 02 '22 at 18:06
  • 1
    `(sizeof(Ts) + ...)` then ;) – Jarod42 Feb 02 '22 at 18:34
  • In case you're using the array as storage for placement-new's keep in mind that you also need to provide proper alignment for the contained objects - so you might need a bit more than `(sizeof(Ts) + ...)` bytes of storage to properly align the individual objects within the array (and an `alignas` on the array) – Turtlefight Feb 02 '22 at 19:12
  • since it's a serialization, there is no problem with alignment, on the contrary, data must be packed. But the size of the buffer depend on each given type, it's not as simple as a sum of all parameters, I have edited my question to precise my needs. – user3897188 Feb 03 '22 at 09:07

2 Answers2

2

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) + ....

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • The size of the buffer depend on each given type, it's not as simple as a sum of all parameters, I have edited my question to precise my needs. – user3897188 Feb 03 '22 at 09:08
  • @user3897188: Then make a constexpr function that takes the sequence of types and computes the appropriate size. This is not a hard problem. – Nicol Bolas Feb 03 '22 at 14:48
  • It's a hard problem for me. Writing the constexpr function that take the parameters pack and return the size is easy, but I don't see how can I use it to write the type parameter of the lambda. template constexpr auto bufferize(Endianness endns, std::array transaction_fun, ARGs... args) won't work since I use args before defining it. – user3897188 Feb 03 '22 at 16:51
  • 1
    @user3897188: It's a template function. You call it with `calc_size()`. Not all template functions deduce the template parameters from arguments. It doesn't need the values of `args`; it needs *types*. – Nicol Bolas Feb 03 '22 at 16:56
0

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

user3897188
  • 293
  • 2
  • 6