I have a struct with multiple overloads of a static function taking some counter<int> as an argument:
struct S {
static void fn(counter<1>);
static void fn(counter<2>);
static void fn(counter<3>);
};
A templated constexpr function can be used to look for a specific overload in that class:
template <typename T>
inline constexpr size_t count_fns() {
// 'defines_fn' is a type trait, full code in demo link
if constexpr (defines_fn<T, counter<99> >::value) { return 99; }
if constexpr (defines_fn<T, counter<98> >::value) { return 98; }
if constexpr (defines_fn<T, counter<97> >::value) { return 97; }
// [...]
if constexpr (defines_fn<T, counter<3> >::value) { return 3; }
if constexpr (defines_fn<T, counter<2> >::value) { return 2; }
if constexpr (defines_fn<T, counter<1> >::value) { return 1; }
return 0;
}
In ordinary usage, count_fns<S>() returns 3, as expected.
However, adding an inline static variable invoking count_fns changes things:
struct U {
static void fn(counter<1>);
static void fn(counter<2>);
static constexpr size_t C0 = count_fns<U>(); // C0 is 2
static void fn(counter<3>);
};
static_assert(count_fns<U>() == 3, " <-- fails, value is actually 'still' 2");
Godbolt suggests this behavior is consistent across compilers (MSVC, gcc, clang): Demo
Is this to be expected, or is it some sort of undefined behavior with the constexpr interpreter?
These are the definitions of counter and defines_fn:
template <size_t Value>
struct counter {
static constexpr size_t value = Value;
};
template <typename T, typename Arg, class = void>
struct defines_fn { static constexpr bool value = false; };
template <typename T, typename Arg>
struct defines_fn<T, Arg, std::void_t<decltype(T::fn(std::declval<Arg>()))> >
{
static constexpr bool value = true;
};