Search code examples
c++macrosvariadic-templatesvariadic-functions

C++ non-type variadic template pack not working with macro function


I have a small example below where the first one works just fine, but then the second one enacts an error where I get the error no member named 'handle_func' in 'test_nontype_variadic'

Working example:

class test_nontype_variadic{
    public:
        template<std::size_t... Sizes, typename UnaryFunction, typename... Args>
        auto handle_func(const std::source_location& sl, UnaryFunction&& unary_op, Args&&... args){
            /* do what needs to be done with Sizes... and sl*/
            return std::forward<UnaryFunction>(unary_op)(std::forward<Args>(args)...);
        }
};

//usage:
int main(){
    test_nontype_variadic test;
    int a = test.handle_func<4, 3, 5>(std::source_location::current(), [](auto a, auto b){return a + b;}, 10, 12);
    std::cout<<a<<std::endl;
}

But if I use a macro to enact a default argument for the variable sl as such, it stops working:

#define _CALLER_EXECUTE_FUNC ff_handle_execute_func
#define handle_func(...) _CALLER_EXECUTE_FUNC(std::source_location::current(), __VA_ARGS__)

class test_nontype_variadic{
    public:
        template<std::size_t... Sizes, typename UnaryFunction, typename... Args>
        auto _CALLER_EXECUTE_FUNC(const char* str, UnaryFunction&& unary_op, Args&&... args){
            /* do what needs to be done with Sizes...*/
            return std::forward<UnaryFunction>(unary_op)(std::forward<Args>(args)...);
        }


};

//usage:
int main(){
    test_nontype_variadic test;
    int a = test.handle_func<4, 3, 5>([](auto a, auto b){return a + b;}, 10, 12);
    std::cout<<a<<std::endl;
}

I get the errors:

error: no member named 'handle_func' in 'test_nontype_variadic'
        int a = test.handle_func<4, 3, 5>(__builtin_FUNCTION(), [](auto a, auto b){return a + b;}, 10, 12);
                ~~~~ ^
error: expected unqualified-id
        int a = test.handle_func<4, 3, 5>(__builtin_FUNCTION(), [](auto a, auto b){return a + b;}, 10, 12);
                                    ^
error: expected ';' at end of declaration
        int a = test.handle_func<4, 3, 5>(__builtin_FUNCTION(), [](auto a, auto b){return a + b;}, 10, 12);

However, if I were to take out the non-type variadic template, it would work:

#define _CALLER_EXECUTE_FUNC ff_handle_execute_func
#define handle_func(...) _CALLER_EXECUTE_FUNC(__builtin_FUNCTION(), __VA_ARGS__)

class test_nontype_variadic{
    public:
        template<typename UnaryFunction, typename... Args>
        auto _CALLER_EXECUTE_FUNC(const char* str, UnaryFunction&& unary_op, Args&&... args){
            /* do what needs to be done with Sizes...*/
            return std::forward<UnaryFunction>(unary_op)(std::forward<Args>(args)...);
        }


};

//usage:
int main(){
    test_nontype_variadic test;
    int a = test.handle_func(std::source_location::current(), [](auto a, auto b){return a + b;}, 10, 12);
    std::cout<<a<<std::endl;
}

That works just fine. I was wondering why, and how it can be fixed.


Solution

  • The syntax:

    test.handle_func<4, 3, 5>(
    

    ... attempts to call the function-like macro handle_func as if it was a member function template, but that obviously can't work. The C++ preprocessor isn't aware of anything like classes, members, and templates. It only understands function-like macros.

    It simply sees handle_func < 4 ... where handle_func is not defined as a non-function macro (so no macro substitution takes place), and < is just some arbitrary token. It doesn't do anything with the parantheses afterwards either.

    Solution

    You could ensure that you're using handle_func like a function-like macro:

    #define handle_func(x, ...) (x)._CALLER_EXECUTE_FUNC(__builtin_FUNCTION(), __VA_ARGS__)
    
    // ...
    
    handle_func(test, [](auto a, auto b){return a + b;}, 10, 12);
    

    That being said, it's unclear why you need macros in the first place. If you were willing to use syntax like:

    test.call()([](auto a, auto b){return a + b;}, 10, 12);
    

    ... then you could avoid macros completely, because .call() could have a parameter

    std::source_location loc = std::source_location::current();
    

    ... and .call() could return a function object with an overloaded call operator into which you can feed the lambda and its arguments. The only thing holding you back, and forcing you into using macros here is seemingly the need for some particular call syntax.

    #include <utility>
    #include <cstddef>
    #include <iostream>
    #include <source_location>
    
    class test_nontype_variadic{
        public:
        auto call(std::source_location loc = std::source_location::current()) {
            return [loc]<typename UnaryFunction, typename... Args>
                (UnaryFunction&& unary_op, Args&&... args)
            {
                // TODO: make use of loc inside here somehow
                return std::forward<UnaryFunction>(unary_op)(std::forward<Args>(args)...);
            };
        }
    };
    
    int main(){
        test_nontype_variadic test;
        int a = test.call()([](auto a, auto b){return a + b;}, 10, 12);
        std::cout << a << std::endl;
    }
    

    See live example at Compiler Explorer

    Note that we need test.call()() because unfortunately, default arguments can't be appended to function parameter packs, at least not if we want deduction to work properly. This is only way to keep it a member function while getting the std::source_location.

    You are a ware of this, judging by your previous questions Can you have a default argument with a class function with variadic templates?