Concise way to specify I do not care about a template argument of my function argument?

Viewed 146

Consider following code:

int64_t signed_vector_size(const std::vector v){
    return (int64_t)v.size();
}

This does not work since std::vector is a template. But my function works for every T!

Easy fix is to just do

1)

template<typename T>
int64_t signed_vector_size(const std::vector<T>& v){
    return (int64_t)v.size();
}

or make the template implicit

2)

int64_t signed_vector_size(const auto& v){
    return (int64_t)v.size();
}

Or concept based solution, option 3.

template<class, template<class...> class>
inline constexpr bool is_specialization = false;
template<template<class...> class T, class... Args>
inline constexpr bool is_specialization<T<Args...>, T> = true;

template<class T>
concept Vec = is_specialization<T, std::vector>;

int64_t signed_vector_size(const Vec auto& v){
    return (int64_t)v.size();
}

I like the second solution, but it accepts any v, while I would like to limit it to the vector type only. Third is the best when just looking at the function, but specifying concepts is a relatively a lot of work.

Does C++20 syntax has any shorter way for me to specify that I want any std::vector as an argument or is the 1. solution the shortest we can do?

note: this is silly simplified example, please do not comment about how I am spending too much time to save typing 10 characters, or how I am sacrificing readability(that is my personal preference, I understand why some people like explicit template syntax).

3 Answers

Note that is not valid in standard C++20, but you can achieve exactly what you want with the following syntax that is supported by GCC as an extension.

std::vector<auto>

Which is shorthand for std::vector<T> where T is unconstrained.

http://coliru.stacked-crooked.com/a/6422d0284d299b85

A template is just a pattern for something. vector is the pattern; vector<int, std::allocator<int>> is a type. A function cannot take a pattern; it can only take a type. So a function has to provide an actual type.

So if you want a function which takes any instantiation of a template, then that function must itself be a template, and it must itself require everything that the template it takes as an argument requires. And this must be spelled out explicitly in the declaration of the function.

Even your is_specialization falls short, as it assumes that all template arguments are type arguments. It wouldn't work for std::array, since one of its arguments is a value, not a type.

C++ has no convenient mechanism to say what you're trying to say. You have to spell it out, or accept some less-than-ideal compromise.

Also, broadly speaking, it's probably not a good idea. If your function already must be a template, what would be the harm in taking any sized_range? Once you start expanding templates like this, you're going to find yourself less likely to be bound to specific types and more willing to accept any type that fulfills a particular concept.

That is, it's rare to have a function that is specific enough that it needs vector, but general enough that it doesn't have requirements on the value_type of that vector too.

How about this syntax?

int64_t signed_vector_size(const instance_of<std::vector> auto& v){
  return (int64_t)v.size();
}

Basically we want to be able to say "this argument should be an instance of some template". So, say that?

template<template<class...>class Z, class T>
struct is_instance_of : std::false_type {};

template<template<class...>class Z, class...Ts>
struct is_instance_of<Z, Z<Ts...>> : std::true_type {};

template<class T, template<class...>class Z>
concept instance_of = is_instance_of<Z, T>::value;

int64_t signed_vector_size(const instance_of<std::vector> auto& v){
  return (int64_t)v.size();
}

that should do it. Note that I don't make a Vec alias; you can pass in partial arguments to a concept. The type you are testing is prepended.

Live example.

Now, I'd actually say this is a bit of an anti-pattern. I mean, that size? Why shouldn't it work on non-vectors? Like, std::spans or std::deques.

Also, instance_of doesn't support std::array, as one of the arguments isn't a type. There is no way to treat type, template and value arguments uniformly in C++ at this point.

For each pattern of type, template and value arguments you'd need a different concept. Which is awkward.

Related