0

I have a static lambda inside a class, that is declared and defined in a header file as:

class A final {
    // inline could be removed of course for splitting
    static inline auto foo = [](auto& param1, auto& param2 auto& param3) {
        // do stuff
        return;
    }
}
// compiles fine

With static variables such as static int x = 42 I can split declaration and definition like this:

// bar.h:
class Bar {
    static int x;
}
// bar.cpp:
#include "bar.h"
int Bar::x = 42;

How can the same thing be archieved with above lambda? Changing the signature would be OK of course.

nada
  • 2,109
  • 2
  • 16
  • 23
  • Only if the signature is changed sufficiently enough so that nothing is `auto`, and once that happy event occurs, simply declare a `std::function`. – Sam Varshavchik Feb 27 '19 at 12:04
  • @SamVarshavchik How would I go about that without destroying all the nice flexibility of that function? – nada Feb 27 '19 at 12:06
  • You mean something like this? in header file: `static auto foo;` and in cpp: `auto Bar::foo = [](auto& param1, auto& param2 auto& param3) { // do stuff return; }` – Iman Kianrostami Feb 27 '19 at 12:31
  • @ImanKianrostami - have you tried if your code compile? – max66 Feb 27 '19 at 12:35
  • @max66 No, I just want to see if this is **SOMETHING LIKE** nada wants. – Iman Kianrostami Feb 27 '19 at 12:41
  • @ImanKianrostami Your idea would be okay, but doesn't compile - it returns `error: declaration of 'auto foo' has no initializer`. – nada Feb 27 '19 at 12:43
  • 1
    why not make it a static member function? The big advantage of lambdas is that you can write the definition in place where you need it, if you want to write the definition somewhere else I dont see the advantage of using a lambda – 463035818_is_not_an_ai Feb 27 '19 at 12:47
  • @user463035818 Would that be pretty? How could it look like provided that it kept its flexibility? – nada Feb 27 '19 at 12:50
  • tbh I doubt whether such a "catch all" overload is a good idea in the first place. What do you actually want to do in `foo`? From the top of my head I dont know what could be a function that can work with any combination of any 3 parameters – 463035818_is_not_an_ai Feb 27 '19 at 12:57
  • note that lambdas are merely syntactic sugar, such that you do not need to write a functor class yourself. Not sure but I think if you did manually write the functor class then you would not have the problem because in contrast to lambdas your own functor has a "well behaved" type in the sense that you can "reuse" it without problems – 463035818_is_not_an_ai Feb 27 '19 at 13:00
  • @user463035818 In my case it takes different types of containers like `std::vector`, `dlib::matrix` and does statsistical stuff with its values and changes them. I had no luck with making a template function out of it. The thing is, you can iterate over all values both, `std::vector` and `dlib::matrix` with a for each loop although `dlib::matrix` might be multi-dimensional (access by [x][y] and vector access by [x]). – nada Feb 27 '19 at 13:06
  • 1
    Why do people ever define lambdas where a plain old function will do? – n. m. could be an AI Feb 27 '19 at 13:12
  • 1
    @n.m. Because the function template syntax is so verbose. Maybe? – eerorika Feb 27 '19 at 13:31
  • @eerorika The thing is, the actual template syntax is more flexible. And when you want to change your lambda a little tiny bit and it doesn't quite fit the syntax, you spend three hours to think how one can do that in the lambda syntax, write 3 SO questions, get 2 downvoted and closed, get 10 unsatisfactory replies, and in the end change it to a normal template syntax anyway, – n. m. could be an AI Feb 27 '19 at 15:22

3 Answers3

3

The basic problem is that every lambda expression has its own separate type (see also this answer). Since you need to know the type to declare a variable and you need to know the lambda expression to know its type, there is no way to declare a variable to hold the result of a lambda expression without knowing the lambda expression itself.

It is possible to declare a variable to hold your lambda and define the variable separately as long as you know the lambda expression in both places. For example:

inline auto makeFoo() {
    return [](auto& param1, auto& param2, auto& param3) {
        // do stuff
        return;
    };
}

class A final {
    static decltype(makeFoo()) foo;
};

decltype(makeFoo()) A::foo = makeFoo();

However, it is impossible to separate the declaration of the variable from the lambda expression (i.e., you can't have the lambda expression only in the file where you put the definition of the variable).

A lambda expression that does not capture anything can be converted to a pointer to a function. If you don't need your lambda to capture anything (like in your example) and only needed a callable for one specific signature, you could simply declare A::foo to be of function pointer type and initialize the definition of A::foo with a matching lambda:

class A final {
    static void (*foo)(int&, float&, double&);
};

void (*A::foo)(int&, float&, double&) = [](auto& param1, auto& param2, auto& param3) {
    // do stuff
    return;
};

If you really need a generic lambda (one with auto parameters) like suggested by your example, that won't work either and you're most likely out of luck. Having a generic call operator means that the operator () function of your closure type must be a function template. Having an operator () function template means that its definition must be known for anyone to actually make a call. Even if you wrote your own class instead of using a lambda expression, there would be no way to have anyone call a generic operator () without knowing its definition. All you could do is declare explicit instantiations of the operator () template for all the signatures you need to support and define those separately. But that, again, requires that you actually know in advance which concrete signatures you need your callable to support…

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
2

How can the same thing be archieved with above lambda?

You can't. You must always declare the type of a variable. If you define the lambda after the declaration of the variable, then the declared type cannot be deduced from the initialiser. But you cannot refer to the type of a lambda before it has been defined because the type is anonymous.

Instead of a lambda (i.e. an anonymous function object), you could simply use a function object of a named type. You can then split the function definition. Also, you must declare the return type of the function, because it cannot be deduced without the definition of the function:

class A final {
    constexpr struct Foo {
        template<class Param1, class Param2, class Param3>
        void operator()(Param1&, Param2&, Param3&) const;
    } foo{};
};

However, as you may notice, the function is in fact a function template. This is because you use auto arguments in the lambda. If you wish to define the template outside the header, then you must constrain the instantiations to a limited set, and explicitly instantiate those where the template is defined:

// the definition
template<class Param1, class Param2, class Param3>
void A::Foo::operator()(Param1&, Param2&, Param3&) const {
    // do stuff
    return;
}

// explicit instantiations
template void A::Foo::operator()<int, int, int>(int&, int&, int&) const;
template void A::Foo::operator()<int, double, float>(int&, double&, float&) const;

If you attempt to call with arguments that haven't been instantiated, in a translation unit where the template isn't defined, then there will be a linker error.

If you wish to keep the arguments unconstrained, then you have conflicting requirements. Unconstrained templates can only be achieved by defining the template in the header.


On the other hand, you might want to consider whether you need a function object in the first place. You haven't demonstrate your need for it. The above example works just as well if you were to make it a a static member function (template) instead of a function object.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Function template declarations can have `auto` return type: http://coliru.stacked-crooked.com/a/dc58a1dde68dafc0. This works without any additional restrictions because once you instantiate the function template, you'll need the actual definition anyway, and the return type can then be deduced for that concrete instantiation like for any other function with return type `auto`. – rubenvb Feb 27 '19 at 14:44
  • @rubenvb but if you're going to hide the definition elsewhere, then your `auto` declaration won't be of much use: http://coliru.stacked-crooked.com/a/c50134252a65c713 – eerorika Feb 27 '19 at 14:46
  • "elsewhere" is not per se after the call site in preprocessed code order. But hmm I was under the impression function template definition needed to go before the first point of instantiation. – rubenvb Feb 27 '19 at 14:51
  • @rubenvb in this case it may be in a separate translation unit altogether, as OP wants to define the function outside the header. Knowing that `auto` return type is OK for separate template definition within the header is a nifty thing to know though. – eerorika Feb 27 '19 at 14:52
  • If I could accept more than one answer, I would have accepted this, too. – nada Feb 27 '19 at 14:58
1

You might still create the functor the old way:

struct Foo
{
    template <typename T1, typename T2, typename T3>
    void operator ()(T1& param1, T2& param2, T3& param3) const;
};

template <typename T1, typename T2, typename T3>
void Foo::operator ()(T1& param1, T2& param2, T3& param3) const
{
    /*..*/
}

class A final {
    // inline could be removed of course for splitting
    static const Foo foo;
};

// in .cpp
const Foo A::foo{};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Note that this will still require the full definition of `Foo::operator()` to be known wherever `A::foo` is supposed to be called since `Foo::operator()` must be a template. You can do it, but doing so doesn't really have any advantages compared to just using a lambda like in the original post (which is no surprise since it's basically just a more explicit way to write the same thing)… – Michael Kenzel Feb 27 '19 at 13:11
  • Interesting. Where would the types T1, T2 and T3 come into action? – nada Feb 27 '19 at 13:12
  • @nada they will normally be deduced when `foo` is called. – Michael Kenzel Feb 27 '19 at 13:13
  • @nada: typo: `auto` -> `Tn`. fixed. – Jarod42 Feb 27 '19 at 13:14
  • @nada note that you will not be able to move the definition of `Foo::operator ()` to a separate .cpp file unless you know exactly *which* specializations you need. Only if this is the case can you declare explicit instantiations and define them in your .cpp file… – Michael Kenzel Feb 27 '19 at 13:16