0

I am working with the below sample code.

auto kernarg = hip_impl::make_kernarg(kernel, std::tuple<Args...>{std::move(args)...});
.
.
.

template <typename... Formals, typename... Actuals>
inline impl::kernarg make_kernarg(
void (*kern)(Formals...), std::tuple<Actuals...> actuals) {
static_assert(sizeof...(Formals) == sizeof...(Actuals),
    "The count of formal arguments must match the count of actuals.");

if (sizeof...(Formals) == 0) return {};

std::tuple<Formals...> to_formals{std::move(actuals)};
impl::kernarg kernarg;
kernarg.reserve(sizeof(to_formals));
.
.
.

when NULL is present as one of the arguments in actuals, the code gives error as below (type of NULL is long int in this case).

grid_launch.hpp:96:28: error: no matching constructor for initialization of 'std::tuple<float *, int, int *>'
std::tuple<Formals...> to_formals{std::move(actuals)};
                       ^         ~~~~~~~~~~~~~~~~~~~~
grid_launch.hpp:165:30: 
note: in instantiation of function template specialization 
'impl::make_kernarg<float *, int, int *, float *, int, long>' 
requested here
auto kernarg = impl::make_kernarg(kernel, std::tuple<Args...> 
{std::move(args)...});

As seen, the expected type of parameter is int* here, but NULL is of type long, so the error.

In such cases, How can we find which argument is NULL in the tuple actuals and fetch it and typecast or do something to match it with the exact type in the tuple to_formals (Here its int*, but it can be any pointer to integral type depending on the situation). Do we need to fetch all the values from to_formals and actuals.

Satyanvesh D
  • 323
  • 1
  • 4
  • 16

2 Answers2

1

Isn't a problem of std::move.

The problem is that C++ is a static typed language. So if you have an assignment of type

std::tuple<Formals...> to_formals{std::move(actuals)};

where a type of the decltype(actuals) tuple is long, the compiler can't evaluate, compile time, if the long value is zero, so convert it to a pointer of same other type, or not.

For this type of problems, C++11 introduced a type for null pointers (std::nullptr_t) and a new keyword to avoid NULL (nullptr).

So, by example, if you have a function as follows

template <typename ... Formals, typename ... Actuals>
void foo (void(*)(Formals...), std::tuple<Actuals...> act)
 {
   std::tuple<Formals...> frm { std::move(act) };
 }

and given a function void(float *, int, int *)

void bar (float *, int, int *)
 { }

calling with NULL

foo(&bar, std::make_tuple(&f0, 0, NULL)); // compilaton error

you get a compilation error (the same of your question, I suppose) but using nullptr

foo(&bar, std::make_tuple(&f0, 0, nullptr)); // compile

If you really want/need use NULL instead of nullptr, the best I can imagine is cast every element of the actuals tuple to the type of the formals tuple; something as follows

template <typename ... Formals, typename ... Actuals, std::size_t ... Is>
void foo_helper (void(*)(Formals...), std::tuple<Actuals...> act,
                 std::index_sequence<Is...>)
 {
   std::tuple<Formals...> frm { (Formals)std::get<Is>(act)... };
 }

template <typename ... Formals, typename ... Actuals>
void foo (void(*pfunc)(Formals...), std::tuple<Actuals...> act)
 {
   foo_helper(pfunc, act, std::make_index_sequence<sizeof...(Actuals)>{});
 }

Now compile also

foo(&bar, std::make_tuple(&f0, 0, NULL));

-- EDIT --

The OP asks

can you please elaborate on the index_sequence being used. Also, do we really need a helper function or it can be done without that

The indexes of the std::index_sequence are needed to extract the values form the act tuple; unfortunately the couple std::index_sequence/std::make_index_sequence is introduced in C++14 but, if you need it in C++11, there is a lot of C++11 alternative implementations.

Unfortunately, the indexes sequence in std::index_sequence need a std::make_index_sequence to be created from a sizeof...(), so a function call is needed, so a foo_helper() function in needed.

Exception.

If your act tuple as exactly one element for every type, you can (starting from C++14) simply write

std::tuple<Formals...> frm { (Formals)std::get<Actuals>(act)... };

directly inside foo(), without defining/calling a foo_helper().

But the no-type-collision clause is a big limit, IMHO.

max66
  • 65,235
  • 10
  • 71
  • 111
  • Thanks for the explanation. can you please elaborate on the index_sequence being used. Also, do we really need a helper function or it can be done without that. – Satyanvesh D Oct 15 '19 at 11:41
  • @SatyanveshD - answer improved; hope this helps. – max66 Oct 15 '19 at 11:59
  • I dont think we get exactly one element for every type in the act tuple, say we might get 2 elements with the type int. so i have to use this helper function. So the rest of the code including to_formals should be put inside the helper or it can still be continued in the actual function. – Satyanvesh D Oct 15 '19 at 13:14
  • @SatyanveshD - there are many `std::make_index` sequence like around there; I'm egocentric so I remember an [answer of mine](https://stackoverflow.com/a/49672613/6022656) that I think (I hope) is clear explaining how can works a simple (but not optimal: it's linear where can be logarithmic) alternative implementation. About exception, you've got the point: if there are two `int` (or two `long`, or two `int *`, etc) in the `Actuals` list, you get an error. – max66 Oct 15 '19 at 13:14
  • @SatyanveshD - I suppose the rest of the code could be in the helper function but you can play a lot with this point. By example, the helper function can be dedicated only to create `to_formals`, returning it as a value. – max66 Oct 15 '19 at 13:17
  • Thanks for your help. I was able to use your code and get it working. But I want to take a look at my answer containing the modified code, and let me know if any improvements can be done. Especially the helper method and the usage of indexSequence and makeIndexSequence. – Satyanvesh D Oct 16 '19 at 13:46
0

Here is what my modified code looks like, which seem to be working. But had to add so much code just to handle the NULL case.

auto kernarg = hip_impl::make_kernarg(kernel, std::tuple<Args...>{std::move(args)...});
.
.
.     
template <std::size_t ...>
struct indexSequence
 { };

template <std::size_t N, std::size_t ... Next>
struct indexSequenceHelper : public indexSequenceHelper<N-1U, N-1U, Next...>
 { };

template <std::size_t ... Next>
struct indexSequenceHelper<0U, Next ... >
 { using type = indexSequence<Next ... >; };

template <std::size_t N>
using makeIndexSequence = typename indexSequenceHelper<N>::type;

template <typename ... Formals, typename ... Actuals, std::size_t ... Is>
std::tuple <Formals...> helper (void(*kernel)(Formals...), std::tuple<Actuals...> act,
                 indexSequence<Is...>){
        std::tuple<Formals...> to_formals{ (Formals)std::get<Is>(act)...};
        return to_formals;
}


template <typename... Formals, typename... Actuals>
hip_impl::kernarg make_kernarg(
void (*kernel)(Formals...), std::tuple<Actuals...> actuals) {
static_assert(sizeof...(Formals) == sizeof...(Actuals),
    "The count of formal arguments must match the count of actuals.");

if (sizeof...(Formals) == 0) return {};

std::tuple<Formals...> to_formals;
to_formals = helper(kernel, actuals, makeIndexSequence<sizeof...(Actuals)>{});

hip_impl::kernarg kernarg;
kernarg.reserve(sizeof(to_formals));
.
.
.
}
Satyanvesh D
  • 323
  • 1
  • 4
  • 16