Search code examples
c++template-meta-programming

Can I check if an expression is type-dependent?


Something tells me the answer is "no", but I figured I'd ask anyway.

I want to check in a macro whether a specific expression is type-dependent. Here's a motivating example:

#include <concepts>
#include <format>
#include <iostream>

template <typename T>
void Print(const T &value)
{
    if constexpr (std::default_initializable<std::formatter<T>>)
        std::cout << std::format("{}", value) << '\n';
    else
        std::cout << "??\n";
}

#define PRINT(...) Print(__VA_ARGS__)

// ---

struct Unknown {};

template <typename T>
void MaybePrint(const T &value)
{
    PRINT(value);
}

int main()
{
    PRINT(42); // "42"
    PRINT(Unknown{}); // "??", but I want a build error, because the expression is not type-dependent.
    MaybePrint(42); // "42"
    MaybePrint(Unknown{}); // "??", this is ok, since the expression is type-dependent.
}

I have a macro that prints any object passed to it (this is for a unit test framework).

If it's not printable, I obviously want a compilation error. But for type-dependent expressions I instead want to print a placeholder (to avoid forcing the user to write if constexpr (printable<T>) ... every time).


Solution

  • Moving the body of Print directly into the macro and tweaking the constraint gives the desired behavior:

    #define PRINT(...)                                                     \
      {                                                                    \
        if constexpr (requires {                                           \
                        std::formatter<                                    \
                            std::remove_cvref_t<decltype(__VA_ARGS__)>>{}; \
                      })                                                   \
          std::cout << std::format("{}", __VA_ARGS__) << '\n';             \
        else                                                               \
          std::cout << "??\n";                                             \
      }
    
    struct Unknown {};
    
    template <typename T>
    void MaybePrint(const T &value) {
      PRINT(value);
    }
    

    https://godbolt.org/z/4Po3xh9oP

    I removed -stdlib=libc++ from Clang because it appears to have lack of library support for this example, but the underlying concept about hard errors vs. substitution failures works the same way in Clang as in GCC and MSVC.

    As pointed out by @SamVarshavchik, you could also move the constraint to the function parameter to get the desired behavior as well:

    template <class T>
    concept printable = std::default_initializable<std::formatter<T>>;
    
    void Print(const printable auto &value) {
      std::cout << std::format("{}", value) << '\n';
    }
    
    struct Unknown {};
    
    template <typename T>
    void MaybePrint(const T &value) {
      if constexpr (printable<T>) {
        Print(value);
      }
    }
    

    https://godbolt.org/z/1MzWhKzrM