How can I get C++ to infer my template typename parameter?

Viewed 286

I'd like to write a type alias template that resolves to the const of the template parameter for most types, like so:

template <typename T>
using TypeAlias = const T;

but for a particular type, say int, it simply resolves to T.

I've tried doing this with std::conditional, as well as specialization of structs containing the type alias, but in all cases, the compiler is unable to infer the type. My questions are: what am I doing wrong in the examples below? and How does one do this correctly?

Edit: I'm working in a very large codebase that handles a large number of types, and adds const to all of them in the definition of TypeAlias (which is actually a more complicated type, basically a container templated over const T). I'm trying to modify this codebase to accept a new type which cannot be const, while making minimal modifications. Explicitly specifying the type in all templated functions like foo isn't a workable solution. What I'm really looking for is a way to modify TypeAlias, and little to nothing else.

example 1:

#include <stdio.h>
#include <type_traits>

template <typename T>
using TypeAlias = typename std::conditional<std::is_same<int, T>::value,
      T, typename std::add_const<T>::type>::type;

template <typename T>
void foo(TypeAlias<T> var) {
  printf("var = %f\n", static_cast<double>(var));
}

int main(int argc, char *argv[]) {
  const int a = 1;
  const float b = 2;
  const double c = 3;

  foo(a);
  foo(b);
  foo(c);

  return 0;
}

example 2:

template <typename T>
struct TypeHolder {
  using type = const T;
};

template <>
struct TypeHolder<int> {
  using type = int;
};

template <typename T>
void foo(typename TypeHolder<T>::type var) {
  printf("var = %f\n", static_cast<double>(var));
}

int main(int argc, char *argv[]) {
  int a = 1;
  const float b = 2;
  const double c = 3;

  foo(a);
  foo(b);
  foo(c);

  return 0;
}
2 Answers

Here's another way to remove const for a specific set of types, just add an overload and use SFINAE to constrain based on whether the deduced type is in the set or not:

#include <iostream>
#include <type_traits>

template <class T, class... Ts>
inline constexpr bool is_any_v = (... || std::is_same_v<T, Ts>);

template <class T>
inline constexpr bool int_or_short_v = is_any_v<T, int, short>;

template <class T>
std::enable_if_t<!int_or_short_v<T>> foo(const T) { std::cout << "const" << std::endl; }
template <class T>
std::enable_if_t<int_or_short_v<T>> foo(T) { std::cout << "non-const" << std::endl; }

int main() {
    const int a = 1;
    const short b = 2;
    const float c = 3;
    const double d = 4;

    foo(a);
    foo(b);
    foo(c);
    foo(d);
}

Try it on godbolt.org

In C++20, even easier:

#include <iostream>
#include <concepts>

template <class T, class... Ts>
concept any_of = (... || std::same_as<T, Ts>);

void foo(const auto) { std::cout << "const" << std::endl; }
void foo(any_of<int, short> auto) { std::cout << "non-const" << std::endl; }

int main() {
    const int a = 1;
    const short b = 2;
    const float c = 3;
    const double d = 4;

    foo(a);
    foo(b);
    foo(c);
    foo(d);
}

Try it on godbolt.org

As was mentioned in the comments, this is a non-deduced context so the template types will not be deduced. This is actually one of the tricks you can use to force the caller so explicitly set the template parameter. What you could do is use if constexpr to differentiate between your two (or more) cases:

void foo_for_int(int x) { /* do something for ints */ }
template <typename T> foo_for_others(T const x) { /* do something for other types */ }

template <typename T>
void foo(T && t)
{
  if constexpr (std::is_same_v<int, T>)
  {
    foo_for_int(t); // no need to forward an int
  }
  else
  {
    foo_for_others(std::forward<T>(t));
  }
}
Related