6

I have a situation where I need to distinguish two overloads, say, foo, using std::enable_if. The condition given to std::enable_if itself depends on a dependent type of the template parameter of foo.

What is the best way to express this using std::enable_if?

The following test code is what I have so far. I realize there are possibly better ways besides std::enable_if to achieve the behavior I want in the test code. However, the following is a simplified version of my use case which itself requires std::enable_if.

#include <type_traits>
#include <cassert>

struct bar
{
  using baz = int;
};

template<class T> struct is_bar : std::false_type {};
template<> struct is_bar<bar> : std::true_type {};

template<class Bar>
struct baz_type
{
  using type = typename Bar::baz;
};


template<class T>
typename std::enable_if<
  std::is_integral<
    typename baz_type<T>::type
  >::value,
  int
>::type
  foo(T x)
{
  return 7;
}

template<class T>
typename std::enable_if<
  !is_bar<T>::value,
  int
>::type
  foo(T x)
{
  return 13;
}

int main()
{
  assert(foo(bar()) == 7);
  assert(foo(0) == 13);

  return 0;
}

The compiler output:

$ g++ --version ; echo ; g++ -std=c++11 repro.cpp 
g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


repro.cpp: In instantiation of ‘struct baz_type<int>’:
repro.cpp:29:3:   required by substitution of ‘template<class T> typename std::enable_if<std::is_integral<typename baz_type<Bar>::type>::value, int>::type foo(T) [with T = int]’
repro.cpp:49:3:   required from here
repro.cpp:18:33: error: ‘int’ is not a class, struct, or union type
   using type = typename Bar::baz;

This code does not compile because the enable_if used in the first overload of foo depends on the nested type T::baz. Because int does not have this nested type, the code is illegal.

What is the correct way to express what I want?

Jared Hoberock
  • 11,118
  • 3
  • 40
  • 76
  • Can you make `baz_type` an alias template rather than a class template? – dyp Mar 17 '15 at 22:59
  • 2
    An alternative is to use several `enable_if`s, e.g. using default template arguments. The first should check `is_bar`, then the second can use `baz_type::type`. The Standard mandates that they're evaluated in lexical order. – dyp Mar 17 '15 at 23:07
  • @DanielFrey *shrug* Have my upvote. Though I wouldn't consider it very readable, at least not after [this answer](http://stackoverflow.com/a/26533335/). – dyp Mar 18 '15 at 00:00

1 Answers1

3

While I wrote the below on Coliru, @dyp already showed the important part in his comment. The following is what will work and what is, IMHO, quite readable:

template<
    class T,
    typename=typename std::enable_if<is_bar<T>::value>::type,
    typename=typename std::enable_if<std::is_integral<typename baz_type<T>::type>::value>::type
>
int foo(T x)
{
  return 7;
}

template<
    class T,
    typename=typename std::enable_if<!is_bar<T>::value>::type
>
int foo(T x)
{
  return 13;
}

Live example

With C++14, one would use std::enable_if_t to make it even shorter.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180