6

I would like to write a generic lambda as a visitor for a variant. The members of this variant contain a constexpr member value, which I would like to use in the visitor. For example:

#include <variant>

template<int r>
struct S {
    constexpr static int this_r = r;
};

int f(std::variant<S<0>, S<1>, S<2> > v) {
    return std::visit([](auto const& arg) {
        if constexpr(arg.this_r == 0) { return 42; }
        else { return arg.this_r; }
    }, v);
}

int g() {
    std::variant<S<0>, S<1>, S<2> > x = S<2>();
    return f(x);
}

GCC is happy to compile this code starting from about version 7.1. Clang, on the other hand, complains that the arg.this_r == 0 argument to the if constexpr is not constant, going back to version 4.0.0 but this is still present in the current trunk.

Who is in the right here and how could I avoid this issue (assuming that a simple if does not cut it because one of the two branches is not instantiable)?

Addendum: Passing arg as a value instead of a const lvalue reference, Clang is happy, but unfortunately this is not an option for me.

Claudius
  • 550
  • 3
  • 14
  • 2
    I think the lambda argument `arg` is not a `constexpr` here. I'm not sure though. – Silvano Cerza Jul 10 '19 at 08:44
  • 1
    `arg` itself is certainly not constexpr, but its member `this_r` is. Once the type of `arg` is known, `arg.this_r` should be constexpr? – Claudius Jul 10 '19 at 08:46
  • 2
    @Claudius no, because `arg.this_r` is a member access expression of a non-constexpr object. `std::remove_cvref_t::this_r` is a constant expression because it doesn't use a member access expression. – Jonathan Wakely Jul 10 '19 at 08:51
  • @JonathanWakely Makes sense, thank you! – Claudius Jul 10 '19 at 08:53
  • @JonathanWakely OTOH, if I pass `arg` by value, Clang sees through it and considers `arg.this_rank` a constant expression (cf. my addendum). Is there some freedom to whether a compiler can consider something a constant expression or is this a case of GCC and Clang being more lenient than required by the standard? – Claudius Jul 10 '19 at 08:57
  • There's no freedom, I think GCC has a bug. When you pass it by reference you're referring to some non-constant object outside the function, which isn't a constant. – Jonathan Wakely Jul 10 '19 at 08:58
  • 1
    Hmm, after looking into it further, maybe Clang has a bug. MSVC, Intel and GCC all agree it's OK: https://godbolt.org/z/0RcsLC – Jonathan Wakely Jul 10 '19 at 09:10
  • 2
    @JonathanWakely I think this is a standard bug, i think it's technically ill-formed, but shouldn't be. – Barry Jul 10 '19 at 09:33

1 Answers1

7

Since this_r is a static member you can always access it without dereferencing (non constexpr) reference to object instance to make clang or other compilers happy:

int f(std::variant<S<0>, S<1>, S<2> > v) {
    return std::visit([](auto const& arg) {
        if constexpr(::std::remove_reference_t<decltype(arg)>::this_r == 0) { return 42; }
        else { return ::std::remove_reference_t<decltype(arg)>::this_r; }
    }, v);
}

online compiler

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • It doesn't exactly win the award for prettiness, but it seems to work and makes sense given @JonathanWakely's explanation. Thank you! – Claudius Jul 10 '19 at 08:54