Erlkoenig
Erlkoenig

Reputation: 2754

Inline ("single-line") function definition in class template instantiation or expression

In C++17, is there a way to pass a "code block" (as a non-capturing lambda or function pointer) to a template parameter in an "inline" fashion without requiring extra lines to define it as a function?

I would like to use that, possibly wrapped in a macro, for defining an API for building a tree-like tuple-based structure.

Assuming

template <void (*FPtr) (int)>
struct Test {
    static void test () {
        FPtr (42);
    }
};

as given, using it as

void test1 (int x) { std::cout << "test1(" << x << ")\n"; }
auto t1 = Test<test1> {};

works, but requires an extra line for the function definition. Using a lambda instead

constexpr auto test2 = static_cast<void (*) (int)> ([] (int x) { std::cout << "test2(" << x << ")\n"; });
auto t2 = Test<test2> {};

works too, but still requires an extra line. I'd like to inline the lambda as

auto t3 = Test<static_cast<void (*) (int)> ([] (int x) { std::cout << "test3(" << x << ")\n"; })> {};

but that results in an error in GCC (I need to use C++17):

lambda-expression in template-argument only available with '-std=c++20' or '-std=gnu++20'

Attempting to wrap both lines into yet another lambda

auto t4 = [] () {
    static constexpr void (*fPTR) (int) = static_cast<void (*) (int)>( [] (int x) { std::cout << "test4(" << x << ")\n"; });
    return Test<fPTR> {};
}

yields

<lambda()>::<lambda(int)>::_FUN' is not a valid template argument for type 'void (*)(int)' because 'static void<lambda()>::<lambda(int)>::_FUN(int)' has no linkage

Using a local class instead of a lambda

auto t5 = [] () {
    struct X {
        static void test (int x) {
            std::cout << "test5(" << x << ")\n";
        }
    };
    return Test<&X::test> {};
} ();

results in

<lambda()>::X::test' is not a valid template argument for type 'void (*)(int)' because 'static void<lambda()>::X::test(int)' has no linkage

Is there some other trick I'm not thinking of? I need to create my Test instance using a single expression without additional declarations to provide an easy-to-use API.

I need it to work in GCC, Clang, and the IAR ARM Compiler (ICC based).

Upvotes: 1

Views: 717

Answers (2)

Erlkoenig
Erlkoenig

Reputation: 2754

I just found out that it is possible to derive from lambdas. That means I can do something like:

template <typename F>
struct Test : F {
    Test (F&& f) : F (std::move (f)) {}
};

and use as

Test ([] () { ... });

Since non-capturing lambda types are empty anyhow, Test is an empty class so using it in an EBO-aware way e.g. using std::tuple won't waste any memory.

Upvotes: 0

lubgr
lubgr

Reputation: 38287

As the compiler says, this is a C++20 feature, "lambdas in unevaluated contexts" (see P0315). gcc has this since version 9, but it obviously requires -std=c++20.clang hasn't implemented this yet.

In pure C++17, there is no way to achieve this.

As a side note, you can force a conversion from a lambda to a function pointer by prefixing the lambda expression with a +, as opposed to the more verbose static_cast variant.

Upvotes: 2

Related Questions