Home > Mobile >  Template function overload with negated condition for std::enable_if_t never considered
Template function overload with negated condition for std::enable_if_t never considered

Time:12-11

I have some template function f() that takes a variadic number of arguments, with an additional goal to keep track of sequences of equivalent types in the variadic argument list. For this, it defers to detail::f() which counts sequences and allows additional customization once the end of sequence is reached. This roughly looks as follows:

#include <cstddef>
#include <type_traits>

namespace detail
{
  template<typename T, size_t N, typename Property, typename... Properties>
  std::enable_if_t<std::is_same_v<T, Property>, void> f()
  {
    return detail::f<T, N   1, Properties...>();
  }

  template<typename T, size_t N, typename Property, typename... Properties>
  std::enable_if_t<!std::is_same_v<T, Property>, void> f()
  {
    return detail::f<Property, 1, Properties...>();
  }

  template<typename T, size_t N>
  void f()
  {
  }
}

template<typename Property, typename... Properties>
void f()
{
  return detail::f<Property, 1, Properties...>();
}

int main(int argc, char **argv)
{
  f<int, int, int, float, float, double, float, float>();
  return 0;
}

See: https://godbolt.org/z/dKo59ebvP

This generates a compiler error though that I do not understand:

candidate template ignored: requirement 'std::is_same_v<int, float>' was not satisfied [with T = int, N = 3, Property = float, Properties = <float, double, float, float>]
  std::enable_if_t<std::is_same_v<T, Property>, void> f()

So once the compiler tries to instantiate f<int, 3, float, ...>() it throws an error, and does not seem to consider the alternative overload for detail::f() for the case where !std::is_same_v<T, Property> holds. Why is this overload not considered?

CodePudding user response:

A qualified name like detail::f used in a function call is not immediately dependent just because the template arguments following it are. detail:: doesn't make it dependent either, so lookup for detail::f is performed at the point of definition of the template, not at the point of instantiation.

(Note that using an unqualified name f instead of detail::f would make the name dependent, but wouldn't change anything about the lookup. The normal unqualified lookup would still be performed from the point of definition and only argument-dependent lookup from the point of instantiation.)

That means that the possible candidates for detail::f in your call detail::f<T, N 1, Properties...>() in the first overload are looked up from right there according to the normal non-dependent lookup rules which don't find any entities declared only later.

So the only possible candidate for the call is the function template in which it appears itself.

You need to forward-declare the other function templates before the call:

namespace detail
{

  template<typename T, size_t N>
  void f();

  template<typename T, size_t N, typename Property, typename... Properties>
  std::enable_if_t<!std::is_same_v<T, Property>, void> f();

  template<typename T, size_t N, typename Property, typename... Properties>
  std::enable_if_t<std::is_same_v<T, Property>, void> f()
  {
    return detail::f<T, N   1, Properties...>();
  }

  template<typename T, size_t N, typename Property, typename... Properties>
  std::enable_if_t<!std::is_same_v<T, Property>, void> f()
  {
    return detail::f<Property, 1, Properties...>();
  }

  template<typename T, size_t N>
  void f()
  {
  }
} 

CodePudding user response:

With templates, the declaration order matters: You call the !std::is_same<T, Property> specialization (#1) from the std::is_same<T, Property> specialization (#2). So the function template #1 must be (forward-)declared before #2:

#include <cstddef>
#include <type_traits>

namespace detail
{
  template<typename T, size_t N>
  void f()
  {
  }

  template<typename T, size_t N, typename Property, typename... Properties>
  std::enable_if_t<!std::is_same_v<T, Property>, void> f()
  {
    return detail::f<Property, 1, Properties...>();
  }

  template<typename T, size_t N, typename Property, typename... Properties>
  std::enable_if_t<std::is_same_v<T, Property>, void> f()
  {
    return detail::f<T, N   1, Properties...>();
  }

}

template<typename Property, typename... Properties>
void f()
{
  return detail::f<Property, 1, Properties...>();
}

int main(int argc, char **argv)
{
  f<int, int, int, float, float, double, float, float>();
  return 0;
}

https://godbolt.org/z/j5Gor899q

  • Related