2

the decltype(__VA_ARGS__) in my macro compiles when only for a single argument and not when multiple arguments are passed.

What I'm trying to achieve is to invoke a variadic function based on a runtime condition. The arguments to my printer could be expensive to compute and therefore I'd like to not evaluate those when my condition is false.

Here's a minimal example below. Any comments to rephrase this question/description are also appreciated.

#include <iostream>
#include <utility>
#include <functional> 

#define PRINT_IF(printer, cond, ...) if(cond) std::invoke(&Printer::print<decltype(__VA_ARGS__)>, printer, __VA_ARGS__)

// Some Fixed Printer Interface ... 
class Printer{
public:

    void print(){}
    
    template <typename T, typename... Types> 
    void print(T&& var1, Types&&... var2) 
    { 
        std::cout << var1 << std::endl ; 
        print(var2...);
    }
    
};


int main()
{
    Printer p;
    
    PRINT_IF(p, returns_false(), very_expensive_string()); // compiles and expensive operation skipped 
    PRINT_IF(p, true, 1); // compiles
    PRINT_IF(p, true, 1, 2, 3); // doesn't compile
    
    // The macro should also handle pointers.
    PRINT_IF(&p, true, 1, 2, 3);

    return 0;
}

Houssam
  • 45
  • 5
  • The problem is that `decltype(__VA_ARGS__)` become `decltype(1, 2, 3)` when you need `decltype(1), decltype(2), decltype(3)`. – max66 Mar 10 '21 at 15:29
  • 1. You don't need this decltype. 2. decltype is unary so can make it work if you put `__VA_ARGS__` in extra parentheses. – bipll Mar 10 '21 at 15:30
  • 1
    Any reason to use `std::invoke` instead of calling `print` on `printer`? – Holt Mar 10 '21 at 15:31
  • why use of macro? I do not see any reason to use it here. – Marek R Mar 10 '21 at 15:35
  • @Holt to handle the cases where `printer` could be either a pointer or an instance – Houssam Mar 10 '21 at 15:36
  • @MarekR The macro is to avoid typing `if(cond) { print(...); }` everywhere which is 4 lines in our style guidelines – Houssam Mar 10 '21 at 15:44
  • But you can do this defining respective function. – Marek R Mar 10 '21 at 15:45
  • @MarekR yes, I've augmented my example with a case where one of the arguments is an expensive operation. In that case I would like to avoid computing this string in case the condition is false. AFAIK if instead of a macro this was a function, the expensive expression would be evaluated anyway. I'm also happy with a suggestion to solve this issue without macros – Houssam Mar 10 '21 at 15:50
  • `cond` is a constant expression (I mean: known at compile time), as in your example, or could be a run-time value? – max66 Mar 10 '21 at 15:55
  • @max66 `cond` is a runtime value. Thanks, I'll edit the question to make this clearer. – Houssam Mar 10 '21 at 15:57
  • @Houssam please edit your question and move any vital information from comments to question content. – Marek R Mar 10 '21 at 16:06

1 Answers1

3

You can do something like this to avoid having to find the template overload:

template <class T>
auto add_pointer(T&& t) {
    if constexpr (std::is_pointer_v<std::remove_reference_t<T>>) {
        return t;
    }
    else {
        return &t;
    }
}

#define PRINT_IF(printer, cond, ...) if(cond) add_pointer(printer)->print(__VA_ARGS__)

This lets ->print deduce the template arguments to avoid having to find an overload and just use add_pointer to convert printer to a pointer if it's not already.

Holt
  • 36,600
  • 7
  • 92
  • 139