Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enable_if not working in Visual Studio when using a constexpr function as argument

I'm currently wrestling with Visual Studio 2017 (compiling using /std:c++latest if that's any help).

The code in question simply selects a struct specialization based on the result of some templated constexpr function. GCC and clang have no trouble compiling it.

Here's my MCVE:

#include <type_traits>

struct A {
  enum {
    test_trait = true
   };
};

template<typename T>
constexpr int choose() {
  return T::test_trait;
}

template<typename T, typename Enable=void>
struct Chosen;

template<typename T>
struct Chosen<T, std::enable_if_t<choose<T>() == 1>> {};

void foo() {
  // This works
  constexpr int chosen = choose<A>();
  static_assert(chosen == 1, "");

  // This resolves to the undefined struct.
  using Chosen_t = Chosen<A>;
  Chosen_t x;
  (void)x;
}

choose() is actually a fair bit more complex in my codebase, but the static_assert still compiles, and checks fine.

I kinda assumed that if the static_assert compiles, there is no reason for the enable_if to not be able to do its magic. Am I wrong? I guess "maybe" T is not technically a dependant type of the enable_if... But if that was the case, I'd expect GCC and clang to slap my wrist.

I can get around this by wrapping the result of choose() in a std::integral_constant, like so:

template<typename T> 
struct Chooser : public std::integral_constant<int, choose<T>()> {};

template<typename T>
struct Chosen<T, std::enable_if_t<Chooser<T>::value>> {};

But I'd really rather not have to jump through that hoop.

Should template resolution be able to resolve this the way I expect? I'm worried that the code is actually wrong, and GCC and clang are just being lenient on me.

like image 711
Frank Avatar asked Sep 10 '17 18:09

Frank


1 Answers

Code still appears to be broken in MSVC 19.00.23506. However, it appears to work with just one more level of indirection (perhaps a better workaround):

template<typename T, bool>
struct ChosenImpl;

template<typename T>
struct ChosenImpl<T, true> {};

template<typename T>
using Chosen = ChosenImpl<T, choose<T>()>;

Demo

A benefit to this is that we are hiding the second template argument from the caller, which they don't care about anyway.

like image 81
AndyG Avatar answered Oct 17 '22 21:10

AndyG