1

I have the following test code

#include <type_traits>
#include <utility>

#define LIFTMEMFN(F)                                                 \
    [](auto&& self, auto&&... args) noexcept(                        \
        noexcept((self).F(std::forward<decltype(args)>(args)...)))   \
        -> decltype(self.F(std::forward<decltype(args)>(args)...)) { \
        return self.F(std::forward<decltype(args)>(args)...);        \
    }

struct Foo {
    template <int k>
    int run(int i, int j) {
        return i + j + k;
    }

    int run2(int i, int j) { return i + j; }
};

int main(int argc, char* argv[]) {
    Foo foo;


    // In this case I need to insert the template keyword
    auto fn2 = LIFTMEMFN(template run<1>);
    int a = fn2(foo, 10, 20);

    // but here not
    int b = foo.run<1>(10, 20);

    return a + b;

}

LIFTMEMFN is designed to lift a member function into a lambda so it can be called like a regular function. However I've discovered a quirk. If I want to lift a member function that is templated when I pass it to the macro I need to use the template keyword

auto fn2 = LIFTMEMFN(template run<1>);
int a = fn2(foo, 10, 20);

however if called directly then I don't

int b = foo.run<1>(10, 20);

If I leave out the template keyword then clang complans

<source>:25:16: error: expected ')'
    auto fn2 = LIFTMEMFN( run<1>);
               ^
<source>:6:61: note: expanded from macro 'LIFTMEMFN'
        noexcept((self).F(std::forward<decltype(args)>(args)...)))   \
                                                            ^
<source>:25:16: note: to match this '('
<source>:6:26: note: expanded from macro 'LIFTMEMFN'
        noexcept((self).F(std::forward<decltype(args)>(args)...)))   \
                         ^
<source>:25:16: error: expected ')'
    auto fn2 = LIFTMEMFN( run<1>);
               ^
<source>:7:62: note: expanded from macro 'LIFTMEMFN'
        -> decltype(self.F(std::forward<decltype(args)>(args)...)) { \
                                                             ^
<source>:25:16: note: to match this '('
<source>:7:27: note: expanded from macro 'LIFTMEMFN'
        -> decltype(self.F(std::forward<decltype(args)>(args)...)) { \
                          ^
<source>:25:16: error: declaration type contains unexpanded parameter pack 'args'
    auto fn2 = LIFTMEMFN( run<1>);
               ^~~~~~~~~~~~~~~~~~
<source>:5:5: note: expanded from macro 'LIFTMEMFN'
    [](auto&& self, auto&&... args) noexcept(                        \
    ^
<source>:25:16: error: expected ')'
<source>:8:57: note: expanded from macro 'LIFTMEMFN'
        return self.F(std::forward<decltype(args)>(args)...);        \
                                                        ^
<source>:25:16: note: to match this '('
<source>:8:22: note: expanded from macro 'LIFTMEMFN'
        return self.F(std::forward<decltype(args)>(args)...);        \
                     ^
<source>:25:16: error: expression contains unexpanded parameter pack 'args'
    auto fn2 = LIFTMEMFN( run<1>);
               ^~~~~~~~~~~~~~~~~~
<source>:8:16: note: expanded from macro 'LIFTMEMFN'
        return self.F(std::forward<decltype(args)>(args)...);        \
               ^                            ~~~~   ~~~~
<source>:26:13: error: no matching function for call to object of type '(lambda at <source>:25:16)'
    int a = fn2(foo, 10, 20);
            ^~~
<source>:25:16: note: candidate template ignored: substitution failure [with self:auto = Foo &, args:auto = <int, int>]: missing 'template' keyword prior to dependent template name 'run'
    auto fn2 = LIFTMEMFN( run<1>);
               ^          ~~~
<source>:5:5: note: expanded from macro 'LIFTMEMFN'
    [](auto&& self, auto&&... args) noexcept(                        \
    ^
6 errors generated.
Compiler returned: 1

gcc complains similarly.

Is there a way to construct the macro so that all three compilers, gcc, msvc and clang can do this without the explicit template keyword?

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • 1
    _"can do this without the explicit template keyword?"_ No. If `self` is a template type (it is), then `self.run` is presumed to be a value and `self.run – Drew Dormann Mar 29 '23 at 16:37
  • For title, [where-and-why-do-i-have-to-put-the-template-and-typename-keywords](https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords?rq=2) should answer the question. For way to construct the MACRO you want, I don't think so. – Jarod42 Mar 29 '23 at 16:39
  • Ok so it is outside the spec when the compiler allows the ``int b = foo.run<1>(10, 20);`` line? The compiler should throw an error here but is being permissive? – bradgonesurfing Mar 29 '23 at 16:46
  • 1
    @bradgonesurfing It about context. In `int b = foo.run<1>(10, 20);` the compiler knows what type `foo` is and seen that it has a `run` function template member and you are calling that. In `(self).F(std::forward(args)...)` which expands to (without template) to `(self).run<1>(std::forward(args)...)` and here is where we lose that context. We don't know what `self` is (until the template get instantiated) so `run` could be a member variable or it could be a function template. We need the `template` to tell the compiler to treat `run` as a function template. – NathanOliver Mar 29 '23 at 16:51
  • Thanks for that explanation. I think it is clearer now. – bradgonesurfing Mar 29 '23 at 23:13

0 Answers0