As my first template metaprogram I am trying to write a function that transforms an input vector to an output vector.
For instance, I want
vector<int> v={1,2,3};
auto w=v_transform(v,[](int x){return (float)(x*2)})
to set w to the vector of three floats, {2.0, 4.0, 6.0} .
I started with this stackoverflow question, The std::transform-like function that returns transformed container , which addresses a harder question of transforming arbitrary containers.
I now have two solutions:
A solution, v_transform_doesntwork that doesn’t work, but I don’t know why (which I wrote myself).
A solution, v_transform that works, but I don’t know why (based on Michael Urman's answer to the above question)
I am looking for simple explanations or pointers to literature that explains what is happening.
Here are the two solutions, v_transform_doesntwork and v_transform:
#include <type_traits>
#include <vector>
using namespace std;
template<typename T, typename Functor,
typename U=typename std::result_of<Functor(T)>::type>
vector<U> v_transform(const std::vector<T> &v, Functor&& f){
vector<U>ret;
for(const auto & e:v)
ret.push_back(f(e));
return ret;
}
template<typename T, typename U>
vector<U> v_transform_doesntwork(const std::vector<T> &v, U(*f)(const T &)){
vector<U>ret;
for(const auto & e:v)
ret.push_back(f(e));
return ret;
}
float foo(const int & i){
return (float)(i+1);
}
int main(){
vector<int>v{1,2,3,4,5};
auto w=v_transform(v,foo);
auto z=v_transform(v,[](const int &x){return (float)(x*2);});
auto zz=v_transform(v,[](int x){return (float)(x*3);});
auto zzz=v_transform_doesntwork(v,[](const int &x){return (float)(x*2);});
}
Question 1: why doesn’t the call to v_transform_doesntwork compile? (It gives a fail-to-match template error, c++11. I tried about 4 permutations of “const” and “&” and “*” in the argument list, but nothing seemed to help.)
I prefer the implementation of v_transform_doesntwork to that of v_transform, because it’s simpler, but it has the slight problem of not working.
Question 2: why does the call to v_transform work? I get the gist obviously of what is happening, but I don’t understand why all the typenames are needed in defining U, I don’t understand how this weird syntax of defining a template parameter that is relied on later in the same definition is even allowed, or where this is all specified. I tried looking up "dependent type names" in cppreference but saw nothing about this kind of syntax.
Further note: I am assuming that v_transform works, since it compiles. If it would fail or behave unexpectedly under some situations, please let me know.
Your doesnotwork expects a function pointer and pattern matches on it.
A lambda is not a function pointer. A stateless lambda can be converted to a function pointer, but template pattern matching does not use conversions (other than a very limited subset -- Derived& to Base& and Derived* to Base&, reference-to-value and vice versa, etc -- never a constructor or conversion operator).
Pass foo to doesnotwork and it should work, barring typos in your code.
template<typename T,
typename Functor,
typename U=typename std::result_of<Functor(T)>::type
>
vector<U> v_transform(const std::vector<T> &v, Functor&& f){
vector<U>ret;
for(const auto & e:v)
ret.push_back(f(e));
return ret;
}
so you call v_transform. It tries to deduce the template types.
It pattern matches the first argument. You pass a std::vector<int, blah> where blah is some allocator.
It sees that the first argument is std::vector<T>. It matches T to int. As you did not give a second parameter, the default allocator for std::vector<T> is used, which happens to match blah.
We then continue to the second parameter. You passed in a closure object, so it deduces the (unnamable) lambda type as Functor.
It is now out of arguments to pattern match. The remaining types use their defaulted types -- U is set to typename std::result_of<Functor(T)::type. This does not result in a substitution failure, so SFINAE does not occur.
All types are determined, and the function is now slotted into the set of overloads to examine to determine which to call. As there are no other functions of the same name, and it is a valid overload, it is called.
Note that your code has a few minor errors:
template<typename T,
typename A,
typename Functor,
typename U=typename std::decay<typename std::result_of<Functor&(T const&)>::type>::type
>
std::vector<U> v_transform(const std::vector<T, A> &v, Functor&& f){
std::vector<U> ret;
ret.reserve(v.size());
for(const auto & e:v)
ret.push_back(f(e));
return ret;
}
which cover some corner cases.
Question 1
Why doesn't the call to v_transform_doesntwork compile?
This is because you've passed it a C++11 lambda. The template argument in v_transform_doesntwork is a function pointer argument. C++11 lambdas are, in fact, objects of an unknown type. So the declaration
template<typename T, typename U>
vector<U> v_transform_doesntwork(const std::vector<T> &v, U(*f)(const T &))
binds T to the input type of the function pointer f and U to the output type of the function pointer. But the second argument cannot accept a lambda for this reason! You can specify the types explicitly to make it work with the non-capturing lambda, but the compiler will not attempt the type inference in the face of the cast.
Question 2
Why does the call to v_transform work?
Let's look at the code you wrote:
template<typename T,
typename Functor,
typename U=typename std::result_of<Functor(T)>::type>
vector<U> v_transform(const std::vector<T> &v, Functor&& f){
Again, T is a template parameter that represents the input type. But now Functor is a parameter for whichever callable object you decide to pass in to v_transform (nothing special about the name). We set U to be equal to the result of that Functor being called on T. The std::result_of function jumps through some hoops to figure out what the return value will be. You also might want to change the definition of U to
typename U=typename std::result_of<Functor&(T const &)>::type>
so that is can accept functions taking constants or references as parameters.
For the doesntwork function, you need to explicitly specify the template parameters:
auto zzz=v_transform_doesntwork<int,float>(v,[](const int &x){return (float)(x*2);});
Then it does work. The compiler is not able to implicitly determine these parameters whilst it converts the lambda to a function pointer.
Related
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).
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.
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
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.
Given
template <typename S, typename T>
T make_T(S const &s) { ... }
How can I leave S to be derived while explicitly providing T?
I would like to be able to say:
auto t = make_T<auto, int>(S{});
but clang and gcc tell me that auto is not allowed in template argument.
Had the arguments happened to be reversed in the prototype of make_T,
then all would be well;
I could explicitly give T and leave S to be derived.
In a previous question,
the proposed solution was to declare a helper function that reversed the arguments, e.g.,
template <typename T, typename S>
T make_T_reversed(S const &s) { return make_T<S,T>(s); }
which now enables
auto t = make_T_reversed<int>(S{});
as desired, but I'm hoping there might be a more direct way that doesn't require creating temporary helper functions. I'm asking as a new question because the accepted answer of the previous question doesn't answer my actual question:
is there a direct means of achieving this?
I'm feeling hopeful that with C++17 and C++20 (not around at the time of the previous question), there may now be, but I've sadly been unable to find it.
Further motivating examples
The use case initially motivating the question was that I wanted to write
std::unordered_set<T, default, default, Allocator> obj;
using the default values for the middle two template parameters
(Hash and KeyEqual),
but explicitly specifying the Allocator parameter.
I'm using the default constructor, so the type for Allocator cannot be derived.
I realise the question I actually asked isn't quite the same (I asked about deriving the values rather than taking the default values), but I'm hoping the same approach would work for both cases:
auto t = make_T<auto, int>(S{});
std::unordered_set<T, auto, auto, Allocator> obj;
if S is from T when T is provided, what you will provide for S?
make_T<auto, int>() is definitely impossible, but make_T<void, int>() may be acceptable for you?
template<typename nS, typename T, typename S = std::conditional_t<std::is_same_v<void, nS>, T, nS>>
T make_T(S const&);
but S is always deduced by the argument, why do you want S to be the first parameter?
or you want S to be determined?
template<typename nS, typename T>
T make_T(std::conditional_t<std::is_same_v<void, nS>, T, nS> const&);
This doesn't work with either gcc-10 or clang-10.
template <typename R, typename T>
auto invoke_function(R (&f)(T), T t) { return std::invoke(f, t); }
invoke_function(std::to_string, 42);
This works with gcc-10, but not clang-10.
template <typename R, typename T>
auto invoke_function(T t, R (&f)(T)) { return std::invoke(f, t); }
invoke_function(42, std::to_string);
Error messages are very similar in all cases: "couldn't infer template argument 'R'" or "couldn’t deduce template parameter ‘R’" (gcc).
It isn't clear why this code is rejected. Since T is deduced, the overload of std::to_string can be determined. The dependency on argument order is particularly annoying. Shouldn't it Just Work?
I know this problem can be sidestepped by introducing a function object:
struct to_string
{
template<typename T> std::string operator()(T t) { return std::to_string(t); }
};
and then just using std::invoke on it. This however requires creating a separate function object for each overload set.
Is there a better way?
It isn't clear why this code is rejected. Since T is deduced, the overload of std::to_string can be determined.
That's not how it works exactly. Template deduction deduces each parameter/argument pair independently first - and then we bring all the deductions together and ensure that they're consistent. So we deduce T from 42 and then, separately, we deduce R(&)(T) from std::to_string. But every overload of std::to_string matches that pattern, so we don't know which one to pick.
But the above is only true if we can deduce each pair independently. If a parameter is non-deducible, we skip it and then try to go back and fill it in later. And that's the key here - we restructure the deduction such that we only deduce T from 42:
template <typename T>
auto invoke_function(std::string (&f)(std::type_identity_t<T>), T t) { return std::invoke(f, t); }
Here, we deduce T and int and now we're deducing std::string(&)(int) from std::to_string. Which now works, because only a single overload matches that pattern.
Except now this is undefined behavior, as per [namespace.std]/6:
Let F denote a standard library function ([global.functions]), a standard library static member function, or an instantiation of a standard library function template. Unless F is designated an addressable function, the behavior of a C++ program is unspecified (possibly ill-formed) if it explicitly or implicitly attempts to form a pointer to F.
std::to_string is not an addressable function.
So the real better way is to just wrap to_string in a lambda and pass that along:
invoke_function([](auto x){ return std::to_string(x); }, 42);
And just adjusting invoke_function to take an arbitrary callable rather than specifically a function. That lambda wrapping generalizes to:
#define FWD(x) static_cast<decltype(x)&&>(x)
#define LIFT(name) [&](auto&&... args) noexcept(noexcept(name(FWD(args)...))) -> decltype(name(FWD(args)...)) { return name(FWD(args)...); }
invoke_function(LIFT(std::to_string), 42);
Seems C++14 auto keyword can be used to appear at the location of function definition as to indicate the return type. In this case, is std::result_of still needed? Isn't it obsolete now?
Yes, absolutely.
Sometimes you want the return type, but not as the result of the function. Let's say I have a vector<X> and I want to apply a function to each element and return the result. This operation is called map or fmap, and we might implement the signature thusly:
template <class T, class F,
class U = std::decay_t<std::result_of_t<F&(T const&)>>>
std::vector<U> map(std::vector<T> const&, F );
You could make the return type auto, but regardless, you need to compute that type U and auto won't give it to you. Any time you want the result of a function call that won't necessarily be the return type, auto won't cut it.
SFINAE. Consider the difference between:
template <class F>
decltype(auto) foo(F f) { return f(0); }
template <class F>
std::result_of_t<F&(int)> bar(F f) { return f(0); }
If F is not invokable with an int, then instantiating foo() is a hard compile error but bar() would simply be removed from the overload set. This can be very valuable in generic code, as you can test expressions for well-formedness.
Annotation. auto tells you nothing about the function return type. If the function is returning the result of invoking one callable with some args, std::result_of_t<F(A, B)> in of itself tells me what that function is doing. auto tells me nothing.
Really a better question may be... why do we need std::result_of if we have decltype. Those seem more closely related. After all, I can do decltype(x+1), how would I express that in terms of result_of?! decltype is clearly better. Well, it turns out there are some slight differences between the two even when it comes to determining the result of a function invocation.
I'm cooking up a vector library and have hit a snag. I want to allow recursive vectors (i.e. vec<H,vec<W,T> >) so I'd like my "min" and other functions to be recursive as well. Here's what I have:
template<typename T>
inline T min(const T& k1, const T& k2) {
return k1 < k2 ? k1 : k2;
}
template<int N, typename T, typename VT1, typename VT2>
inline vec<N,T> min(const container<N,T,VT1>& v1, const container<N,T,VT2>& v2) {
vec<N,T> new_vec;
for (int i = 0; i < N; i++) new_vec[i] = min(v1[i], v2[i]);
return new_vec;
}
...
template<int N, typename T>
class vec : public container<N,T,vec_array<N,T> > {
...
// This calls the first (wrong) method and says you can't call ? on a vec
vec<2,float> v1,v2;
min(v1,v2);
// This says the call is ambiguous
container<2,float,vec_array<2,float> > c1,c2;
min(c1,c2);
// This one actually works
vec<2,float> v3; container<N,T,some_other_type> v4;
min(v3,v4);
// This works too
min<2,float,vec_array<2,float>,vec_array<2,float> >(v1, v2);
That last call is ugly! How can I call the right method with just min(v1,v2)? The best I can come up with is to get rid of the "vec" class (so v1 and v2 have to be defined as container<2,float,vec_array<2,float> >) and add one more template<N,T,VT> min method that calls min<N,T,VT,VT>(v1,v2).
Thanks!
You are going to have an overload resolution that prefers the first min for the first case. It accepts both arguments by an exact match, while the second min needs a derived to base conversion to accept arguments.
As you have subsequently figured out (by experimentation?), if you use container<...> as argument types, instead of derived classes, this won't need a derived to base conversion anymore, and overload resolution will then prefer the second template because otherwise both are equally well accepting the arguments but the second template (In your own solution) is more specialized.
Yet in your own solution, you need to put a typename before the return type to make the solution Standard C++. I think the problem that causes you to need to define a second template is that in order to make the template more specialized, the first min min needs to accept all the arguments that the second template accepts, which is figured out by just trying to match second template's arguments against first
container<N, T, VT1> -> T // func param 1
container<N, T, VT2> -> T // func param 2
So, the different template parameter types try to deduce to the same template parameter, which will cause a conflict and make the first template not successfully deduce all argument of the second template. For your own solution, this won't be the case:
container<N, T, VT> -> T // func param 1
container<N, T, VT> -> T // func param 2
This will make the first template deduce all the parameter types from the second template, but not the other way around: container<N, T, VT> won't match an arbitrary T. So your own solution's template is more specialized and is called, and then explicitly forwards to the other template.
Finally note that your own solution only accepts containers where the third template argument is the same, while your other min template accepts containers where that argument can be different for both function arguments. I'm not sure whether that's on purpose - but given the other min function in place which conflicts if you won't make the third argument types the same as shown above, I'm not sure how to otherwise fix that.
Questioner subsequently edited his own answer , so most of my references above to "your own answer" don't apply anymore.
template<typename T1, **typename T2**>
inline T1 min(const T1& k1, **const T2&** k2) {
return k1 < k2 ? k1 : k2;
}
...
template<int N, typename T>
struct vec {
typedef container<N,T,vec_array<N,T> > t;
};
...
vec<2,float>::t v1,v2;
min(v1,v2);
That's what I finally did to get it to work.
The ambiguity was because both arguments have the same type - container<2,float,vec_array<2,float> >. That's one point for the min(const T&,const T&) method. Since min(const container<N,T,VT1>& v1, const container<N,T,VT2>& v2) is a match and more specialized, it also got an extra point and the compiler couldn't make up its mind over which one to use. Switching the generic min to use two type arguments - min(const T1&, const T2&) - beats it into submission.
I also switched to using a "template typedef" instead of inheritance to define vec<N,T>'s without having to deal with the messy container<N,T,VT> stuff. This makes vec<N,T>::t be an exact match to the correct function.
Now that I'm using a typedef rather than inheritance and two types in the generic min function instead of just one, the correct method is getting called.