1

So I know you can use enable_if to disable a function template (e.g. if the type is not integral) - but can you do the same with constexpr?

i.e. is there an equivalent for this:

template<class T, typename = std::enable_if_t<std::is_integral_v<T>> >
T test2()
{
    return {};
}

with constexpr - something like:

template <typename T>
T test()
{
    // we expect various integral and floating point types here
    if constexpr (std::is_integral_v<T>)
    {
        T output{};
        return output;
    }
    else
    {
       // trigger compiler error
       return{};
    }
}

so I want:

test(1); // compile ok
test(std::string("hello")); // compile fail

Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
code_fodder
  • 15,263
  • 17
  • 90
  • 167

2 Answers2

4

You can use static_assert with a type-dependent expression that is always false in constexpr if.

template<class> inline constexpr bool dependent_false_v = false;
template <> inline constexpr bool dependent_false_v<decltype([](){})> = true; // requires C++20
template <typename T>
T test(T)
{
    // we expect various integral and floating point types here
    if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>)
    {
        T output{};
        return output;
    }
    else
    {
       // trigger compiler error
       static_assert(dependent_false_v<T>);
    }
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 1
    May as well just do `static_assert(std::is_integral_v)` and get rid of the `if constexpr`. – Raymond Chen Oct 14 '21 at 15:16
  • The dependent false is attempting to fool your compiler. I am unaware of anything in the standard that makes it different than `static_assert(false)`? Did the standard get changed since I last looked at it, or do I misunderstand? (I get you are trying to make the false dependent on the type passed, but the rules about something always being false are more strictly worded whenever I go through them than that...) – Yakk - Adam Nevraumont Oct 14 '21 at 15:21
  • @Yakk-AdamNevraumont [the discarded statement can't be ill-formed for every possible specialization](https://en.cppreference.com/w/cpp/language/if#Constexpr_if), isn't it? – songyuanyao Oct 14 '21 at 15:23
  • @songyuanyao yes, and in your case, the discarded statement is ill-formed for every possible specialization. The "dependent false" attempts to disguise this fact from the compiler detecting it; but your code is still ill-formed, ndr. Or at least this was my conclusion last time I tried to standard delve that trick. – Yakk - Adam Nevraumont Oct 14 '21 at 15:25
  • 1
    http://eel.is/c++draft/temp.res#6.1 "The program is ill-formed, no diagnostic required, if: (6.1) no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template [...]" -- there is "no valid specialization" of your `else` substatement. Your example code is ill-formed, ndr. All you did with your `dependent_false_v` is obscure it so the compiler didn't generate a diagnostic. – Yakk - Adam Nevraumont Oct 14 '21 at 15:29
  • @Yakk-AdamNevraumont You mean there's no specialization defined for `dependent_false_v`, so it's always `false` and the discarded statement is ill-formed ? – songyuanyao Oct 14 '21 at 15:32
  • @songyuanyao No, for the `test` template; there is a `constexpr if` substatement for whom there is no valid instantiation; `static_assert(dependent_false_v)` has no valid instantiation. The existence of such a substatement of a `constexpr if` means that, if that substatement isn't instantiated, your program becomes ill-formed, no diagnostic required. If that substatement is instantiated, it instead becomes ill-formed due to the `static_assert`. This is the exact same logic for `static_assert(false)` and `static_assert(dependent_false_v)`. – Yakk - Adam Nevraumont Oct 14 '21 at 15:34
  • In the `static_assert(false)` case the compiler, despite not being *required* to tell you your program is ill-formed, does so. When you `static_assert(depdenent_false_v)` you manage to **fool** the compiler, so it doesn't detect the program is ill-formed, so it doesn't generate the diagnostic. The program remains ill-formed, you just bypassed the compiler trying to help you and tell you you screwed up. In 99.9% of cases, `dependent_false_v` tricks do this and only this; they prevent a helpful diagnostic of an ill-formed program. – Yakk - Adam Nevraumont Oct 14 '21 at 15:35
  • @Yakk-AdamNevraumont Does that mean cppreference.com is wrong? It's showing this as a common workaround in serveral places. – songyuanyao Oct 14 '21 at 15:39
  • @songyuanyao As far as I know, yes, it is wrong. There are a handful of places where making something a dependent type actually matters, but almost all uses I know of don't match those places. (I don't recall what they are off hand) – Yakk - Adam Nevraumont Oct 14 '21 at 15:39
  • Otherwise, make it [well-formed](https://stackoverflow.com/a/57787933/11638718)? – 康桓瑋 Oct 14 '21 at 15:46
  • So we have to define a specialization with value `true` ? @康桓瑋 – songyuanyao Oct 14 '21 at 15:50
  • this is exactly what I asked for: +1 - but just for simplicity the static assert (on its own) is probably nicer – code_fodder Oct 14 '21 at 15:52
  • 1
    @康桓瑋 It is an interesting question if those hacks succeed. If there is no way to form the "valid" specialization of the template because the only valid type that would make it valid cannot be named in the context of the template, is there a valid specialization of the template? What is the sound of one type clapping? – Yakk - Adam Nevraumont Oct 14 '21 at 15:55
4

It is as simple as this:

template <typename T>
T test()
{
  static_assert(std::is_integral_v<T>);
  T output{};
  return output;
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524