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&);
Related
I have a function like this to implement fmap for C++:
// Given a mapping F from T to U and a container of T, return a container of U
// whose elements are created by the mapping from the original container's
// elements.
template <typename F, template <typename...> typename Container, typename T>
Container<std::invoke_result_t<F&, const T&>> Fmap(F&& f,
const Container<T>& input);
The idea is to use a template template parameter (Container) to allow accepting any STL-like container. All of the ones in the actual STL I've tried work fine, but a custom container in our codebase doesn't work because it accepts a non-type template parameter
template <typename Key, int Foo = 256>
class MyContainer;
This causes a substitution failure from clang:
template template argument has different template parameters than its corresponding template template parameter
Is there a way to abstract over all template parameters, not just types? If not, is there a better way to structure my code to allow doing what I want without specializing for MyContainer and all others like it in particular?
A template template parameter can only match one kind of template; that kind is determined by the template parameter list. You have to write another version of Fmap if you want to accept MyContainer. However, if you do, you can match any template that has one type parameter followed by any number of non-type parameters: it could be an int like in your example, or it could be a char and a bool...
template <typename F, template <typename, auto...> typename Container, typename T, auto ...Vs>
Container<std::invoke_result_t<F&, const T&>, Vs...> Fmap(F&& f, const Container<T, Vs...>& input) {
return {};
}
Demo
To add up to the general suggestion that was already given (overload for each container), I think you'd get quite a good overview of one way that thing can be accomplished by exploring how Louis Dionne's Boost.Hana defines Functor and other concepts (yeah, there are also Applicative, Monad, Comonad and others), and how types like boost::hana::basic_tuple or boost::hana::optional (a compile-time optional) implement it. The interesting part to read with respect to your question is probably tag dispatching. Here is a complete example of how to make std::array a hana::Functor, so that you can apply hana::transform on it.
Another interesting reading that could broaden your view on this topic, is P1895, which is also liked off a post by Barry Revzin on the topic (not exactly in favour of the tag_invoke approach that the linked proposal is about).
Some time ago, I also asked a question about this topic, but I haven't received a satisfying answer yet.
Since your function does not know the kind of template parameters of the template template parameter, and cannot use them, they all must have defaults in the actual template argument. You can exploit this fact by creating and using an alias template:
template <typename F, template <typename /* no pack */> typename Container, typename T>
Container<std::invoke_result_t<F&, const T&>> Fmap(F&& f,
const Container<T>& input);
template <typename X> using MyContainerDefault = MyContainer<X>;
something = Fmap(someFunction, MyContainerDefault);
Having said that, there is a good reason why standard library algorithms do not accept or return containers. There are many resources that explain this, just search for why stl algorithms do not work with containers.
Another point worth mentioning is that in Haskell each "container" (functor) implements fmap in its own way, so one general implementation of Fmap is rather dubious. If you want to clone fmap, it should have an overload for each container or be a container's member function. Once you accept this, you no longer need the template template parameter because each container knows its own template parameters.
Consider any of the common type-level algorithms provided by libraries such as Boost.MP11, Brigand, etc...
For instance:
template<typename... Args>
struct TypeList;
using my_types = TypeList<int, float, char, float, double>;
constexpr int count = boost::mp11::mp_count_if<my_types, std::is_floating_point>::value;
// this holds:
static_assert(count == 3);
Notice that std::is_floating_point could be defined as:
template<typename T>
struct is_floating_point { constexpr bool value = __compiler_magic(T); };
And likewise, we have the std::floating_point concept
template<typename T>
concept floating_point = requires (T t) { __other_compiler_magic(T); };
Sadly, despite the similarity, there does not seem to be an easy way to write something like this without introducing a manually-named wrapper for the concept:
constexpr int count = boost::mp11::count_if<my_types, std::floating_point>::value;
My question is: why cannot concepts be passed in place of types at this point ? Is it a lack of standardization, or is it something that these libraries can solve by providing more overloads ?
It looks like every concept has to be wrapped in a templated type which will just call the concept on its template argument.
From the outside, concepts just look like meta-functions whose domain is {set of types} -> bool. Compilers are able to delay passing parameters to "traditional" type-based metafunctions such as std::is_floating_point, why can't the same seem to happen with concepts ?
The literal answer is that we have template template parameters but not concept template parameters, so you can't pass a concept as a template argument.
The other literal answer is that it was never part of the original concepts proposal and nobody has put in the effort to suggest it as an extension (although I've been collecting use-cases).
One thing that would have to be answered is how dependent concepts affect subsumption - since currently use of concepts is never dependent and so figuring out subsumption is straightforward (actually, it's still not straightforward at all, but at least all the things you need are right there). But in a scenario like:
template <template <typename> concept C, typename T>
requires C<T>
void foo(T); // #1
template <typename T>
void foo(T); // #2
Probably if #1 is viable, you want to say it's a beter candidate than #2 since it's still constrained while the other is not. Maybe that's trivial. But then:
template <template <typename> concept C, typename T>
requires C<T>
void bar(T); // #3
template <OtherConcept T>
void bar(T); // #4
Let's say #3 and #4 are both viable, is it possible to say which is better? We generally say a whole overload is always better than a different one - but that might not be the case here. Maybe this is just ambiguous?
That seems to me like the main question that would need to be answered in order to get concept template parameters.
The other question might be, can I write foo<convertible_to<int>>(42). convertible_to<int> isn't really a unary concept, but it is a type-constraint that is treated as one in certain contexts, so I would still expect that to work.
Once we have such a thing, I'm sure Boost.Mp11 will quickly acquire something like:
template <template <typename...> concept C>
struct mp_quote_c {
template <typename... T>
using fn = mp_bool<C<T...>>;
};
So that you can write:
constexpr int count = mp_count_if_q<my_types, mp_quote_c<std::floating_point>>::value;
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.
I'm in trouble for the way I should reference arguments in a template (honestly, I strongly suspect that it's not possible to achieve what I'd like to do).
It follows an example of what I'd like to do (of course, this is not syntactically legal, the aim is to give the idea of which is the target):
template<class C, Ret(C::*Member)(Params...), typename Ret, typename... Params>
class MyClass { }
In other terms, I'd like to reference a member of a class, by specifying at the same time also which is the returned value and the parameters of that method.
Unfortunately, the only way I see to do that is something like the following one (well, it depends indeed on where those typenames are required, anyway it may be a meaningful example):
template<typename Ret, typename... Params>
class MyClass {
public:
template<class C, Ret(C::*Member)(Params...)>
MyClass(C *c) { /* do something else and give sense to this class */ }
}
Besides the one above, that is to break the interlacing by introducing a templated constructor, there exists another valid approach to obtain the same result with the sole class template signature?
I know (pretty simple) how to achieve that in case of not variadic template (as an example, move Ret before Member), but the variadic one (Params) has to lay at the end of the template list and I cannot refer it in any way.
According with this question, a viable solution could be to rely on deduction forced by a default value.
As an example, the following code should be valid:
class MyClass {
public:
template <class C, typename R, typename... P, R(C::*M)(P...) = &C::foo>
void bar(C *c) { }
};
I cite part of the linked question (a citation that is a citation for itself, I'm in a loop):
A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument.
Because of that, the following code should not be allowed instead, even if it compiles with GCC:
class MyClass {
public:
template <class C, typename R, typename... P, R(C::*M)(P...)>
void bar(C *c) { }
};
Well, quite tricky, not so flexible a solution and honestly I ended up far ago with a bit of refactoring, but for the sake of clarity I've decided to add an answer and close the question with a snippet that compiles.
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.