Search code examples
g++c++17clang++boost-hanaif-constexpr

How to properly check for member at compile time with boost::hana?


I am writing a physics simulation program and I want to do the following: I have a hana adapted struct and I want to check if this struct has member called "absorbedEnergy" at compile time using:

if constexpr ( ... )

What is the proper way of doing that in c++17 which I use?

Now using hana documentation I have came up with this:

struct HasAE { double absorbedEnergy };
struct HasNoAE {};

temaplate<typename Cell>
void irelevantFunction(Cell& cell){
    auto has_absorbedEnergy = hana::is_valid(
        [](auto &&p) -> decltype((void) p.absorbedEnergy) {});

    if constexpr(has_absorbedEnergy(cell)) { ... }
}

HasAE cell;
HasNoAE anotherCell;
cell.absorbedEnergy = 42; //value known at runtime

irelevantFunction(cell);
irelevantFunction(anotherCell);

The thing is that this compiles just fine with g++ 7.4.0 and does what I expect but fails to compile with clang++-8. It gives an error:

constexpr if condition is not a constant expression

I suspect that this originates from the fact that argument of has_absorbedEnergy - cell is not and constant expression. Is there a way around this?


Solution

  • Your issue seems to be related to the requirement in the standard for the expession in if constexpr to be "contextually converted constant expression of type bool" (see this question). You can work around that by changing the if constexpr to:

    if constexpr (decltype(has_absorbedEnergy(cell)){})
    

    https://wandbox.org/permlink/hmMNLberLJmt0ueJ


    Alternatively, you can use expression SFINAE to achieve what you want (see the cppreference.com documentation of std::void_t):

    #include <type_traits>
    #include <iostream>
    
    template <typename, typename= std::void_t<>>
    struct has_absorbedEnergy : std::false_type {};
    
    template <typename T>
    struct has_absorbedEnergy<T,
      std::void_t<decltype(std::declval<T&>().absorbedEnergy)>>
        : std::true_type {};
    
    template <typename Cell>
    void irelevantFunction([[maybe_unused]] Cell &cell) {
      if constexpr (has_absorbedEnergy<Cell>::value)
        std::cout << "Has absorbedEnergy\n";
      else
        std::cout << "Does not have absorbedEnergy\n";
    }
    
    struct HasAbsorbedEnergy
      { int absorbedEnergy; };
    
    struct DoesNotHaveAbsorbedEnergy
      {};
    
    int main()
    {
    HasAbsorbedEnergy Has;
    DoesNotHaveAbsorbedEnergy DoesNot;
    
    irelevantFunction(Has);
    irelevantFunction(DoesNot);
    }
    

    https://wandbox.org/permlink/0559JhpVQBOwHC0Z