Pass C++20 Templated Lambda to Function Then Call it

Viewed 179

I'm trying to pass a templated lambda to a function and then call it with a template parameter, to enable specialization of that function for custom types.

But when I try to call the lambda I get this error: error: invalid operands to binary expression

Here's a godbolt link for anyone who wants to play around with this: https://gcc.godbolt.org/z/qYPcea

#include <cstdint>
#include <string>
#include <cstring>

enum class Alignment : uint8_t {
    
    one,
    two,
    four,
    eight
};

template <Alignment alignment, typename T>
static void align(T& pointer)
{
    intptr_t& value = reinterpret_cast<intptr_t&>(pointer);
    value += (-value) & ((uint64_t)alignment - 1);
}

template<typename Lambda, typename T>
static void specialization(Lambda&& lambda, const T& t) 
{
   lambda<Alignment::eight>(t.data(), t.size());
}

int main()
{
    uint8_t buffer[1024];

    void *writeTo = buffer;

    auto lambda = [&] <Alignment alignment> (const void *input, uint32_t inputSize) -> void
    {
        align<alignment>(writeTo);
        writeTo = memcpy(writeTo, buffer, inputSize);
    };
    
    std::string input("helloworld");

    specialization(lambda, input);

    return 0;
}
2 Answers

The issue is that lambdas are not class templates, they're just regular classes where the member call operator, i.e. operator() is templated.

When the template parameters are deduced for a generic lambda, this distinction is not noticeable (which is a very good thing).

So in your example, lambda is not a class template, but you are using syntax that would be used for a class, not a member function.

If you want to specify the template parameters for a lambda explicitly, you'll need to say that you're calling the member operator() of lambda, and you'll need to say that it's a template to disambiguate.

lambda.template operator()<Alignment::eight>(t.data(), t.size());

Here's the version of your code that compiles.

I hear you have a problem with lambdas, kid.

Have you considered fixing it with ... more lambdas?

template<auto k>
constexpr std::integral_constant<std::decay_t<decltype(k)>, k> constant = {};

auto TemplateInvoke = [](auto&& f) {
  return [&](auto...constants) {
    return [&](auto&&...args)->decltype(auto) {
      return f.operator()<decltype(constants)::value...>(decltype(args)(args)...);
    };
  };
};

there we go.

template<typename Lambda, typename T>
static void specialization(Lambda&& lambda, const T& t) 
{
  TemplateInvoke(lambda)(constant<Alignment::eight>)(t.data(), t.size());
}

All problems with lambdas can be solved with more lambdas.

The problem is that lambda<template args>(stuff) doesn't work; you need to lambda.operator()<template args>(stuff). This syntax is awkward.

The above attempts to make it less awkward by creating effectively a different dialect of C++ where you pass template arguments as function arguments.

Some may consider this overkill.

They are probably the type of people who think "a lambda that returns a lambda that returns a lambda and takes a lambda as an argument" is confusing for reasons above and beyond ambiguities of grammar.

Related