I'm learning templates. If I mix up the concepts template / template-type / template-argument, please correct me.
I'm trying to write a template function that creates an object and returns it. The type of the object comes from the template argument that has to be explicitly specified.
result = createObject<ObjectType>();
This object though is supposed to be a template. A container for example. And the function is supposed to know the type of the object and its template arguments. Ex:
result = createObject<Container<ElementType>>();
I've tried to solve it with template template parameter:
template <template<class> class ContainerType, class ElementType>
auto createObject()
{
ContainerType<ElementType> result;
//do stuff...
return result;
}
//...
template<typename T>
struct Vector{};
//...
//const auto random_vec = createObject<Vector<float>>(); // ERROR.
const auto random_vec = createObject<Vector, float>();
The second case works, the first doesn't. It says candidate template ignored: invalid explicitly-specified argument for template parameter 'ContainerType'.
Is it possible to make it work like the first case? Give it something like Vector<float> and it can deduce the ContainerType to Vector and ElementType to float? Is it possible to overload or specialize this function so that it handles certain types of containers differently? Should I use concepts?
The usual way to do decomposition like this is via partial specialization, which requires a helper class template:
namespace detail {
template<class> struct create; // undefined
template<template<class T> class C,class T>
struct create<C<T>> {
static C<T> make() {/* … */}
};
}
template<class T>
T createObject() {return detail::create<T>::make();}
The primary template can be defined if you want to support the general case, and other specializations may be added for other kinds of templates like std::array.
You could create a type trait to check if the type is instantiated from a template:
#include <type_traits>
// trait to check if the type is instantiated from a template
template<typename T>
struct is_template_instance_type : std::false_type {};
template<template<class,class...> class C, class T, class... Rest>
struct is_template_instance_type<C<T,Rest...>> : std::true_type {
using class_type = C<T,Rest...>;
using value_type = T;
// using rest_types = std::tuple<Rest...>;
};
// Helper variable template - if needed for something later
template<class T>
inline constexpr bool is_template_instance_type_v = is_template_instance_type<T>::value;
You could then add overloads:
template<class T, class C = is_template_instance_type<T>, class U = typename C::class_type>
auto createObject() {
U result;
// typename C::value_type x; // if you need the value type
return result;
}
template<template<class,class...> class C, class T, class... Rest>
auto createObject() {
return createObject< C<T,Rest...> >();
}
And it would then work with Vector<float>, Vector, float but not float for example.
Demo
You can simply go like this:
template<typename T, typename V = typename T::value_type>
T createObject()
{
T t {}; // T will be e.g std::vector<int>
V v {}; // V will be int
// do work...
t.push_back(v++);
t.push_back(v++);
// ...work done
return t;
}
Than you can use it like this:
int main ()
{
auto obj1 = createObject<std::vector<int>>();
auto obj2 = createObject<std::list<double>>();
return 0;
}
Related
I am new to C++20. The intention here is to have a template class which has value whose type would be the underlying type of T that's passed in.
So in case of T being:
std::optional<char>, it's char value
int, it's just int value.
Is there any better way to extract the types than through struct TypeExtract? More or a generic solution in C++20 perhaps? Given if the class could take more than just std::optional<int> or just a primitive type?
Can the condition in foo be improved specially with the way val is initialized?
template<typename T>
constexpr bool is_optional = false;
template<typename T>
constexpr bool is_optional<std::optional<T>> = true;
template<typename T>
struct TypeExtract
{
using type = T;
};
template<typename T>
struct TypeExtract<std::optional<T>>
{
using type = T;
};
template <typename T>
concept is_integral = std::is_integral_v<typename TypeExtract<T>::type>;
template <is_integral T>
class A
{
using Type = typename TypeExtract<T>::type;
Type val;
void foo(T value)
{
if constexpr (is_optional<T>)
{
val = *value;
}
else
{
val = value;
}
}
};
int main()
{
A<char> a1;
A<std::optional<int>> a2;
// A<double> a3; // fails
}
It looks like you're trying to extract first template parameter from a class template and keep on unwinding templates until you get to a non-template type. In that case you could make a type trait that is specialized for types instantiated from templates:
// primary template
template<class T, class...>
struct type {
using value_type = T;
};
// specialization for template instantiated types
template<template<class, class...> class T, class F, class... Rest>
struct type<T<F, Rest...>> {
using value_type = typename type<F>::value_type;
};
// helper alias
template<class... Ts>
using type_t = typename type<Ts...>::value_type;
You could then use it like so:
int main() {
type_t<char> a1;
type_t<std::optional<int>> a2;
type_t<double, int> a3;
static_assert(std::is_same_v<decltype(a1), char>);
static_assert(std::is_same_v<decltype(a2), int>);
static_assert(std::is_same_v<decltype(a3), double>);
}
There is no good or bad here, it's a matter of style and convention, but personally I would get rid of if constexpr and take advantage of trailing requires for the sake of reducing function's cyclomatic complexity. On the other hand, that add some boilerplate. The choice is yours.
Not much can be done about type extraction, though I would probably use a templated base and import its member(s) instead of importing the type into the class. Not a big deal, but it feels more idiomatic to me.
As for concepts, I'd probably use more library-provided ones in the place of type traits.
Side note: consider using assert, .value() or similar function when assigning from the optional to ensure it's not empty.
All in all, I'd probably write your code somewhat this way:
#include <concepts>
#include <type_traits>
#include <optional>
template<typename T>
concept StdOptional = std::same_as<std::optional<typename T::value_type>, T>;
template<typename T>
concept OptionalIntegral = StdOptional<T> and std::integral<typename T::value_type>;
template<typename T>
concept OptionalOrOptionalIntegral = std::integral<T> or OptionalIntegral<T>;
template<typename>
struct ABase;
template<std::integral T>
struct ABase<T>
{
T value;
};
template<OptionalIntegral T>
struct ABase<T>
{
typename T::value_type value;
};
template<OptionalOrOptionalIntegral T>
class A : ABase<T>
{
using ABase<T>::value;
public:
void setValue(T val) requires(std::integral<T>)
{
value = val;
}
void setValue(T val) requires(OptionalIntegral<T>)
{
value = val.value();
}
};
Demo: https://godbolt.org/z/dzvr9xbGr
Consider the following piece of C++ code:
int main(){
MyObject<int> obj;
foo(obj);
}
template <typename T>
void foo(T& objct){
...
}
In foo, the type of objct will be MyObject<int>.
I would like to create a variable in foo() whose type is the objct's generics, in this case, int.
Is there a way to do that? Thank you.
Edit
Unfortunately (I think) I can't rewrite the signature because the function foo() is called with different type of objects, for example
int main(){
MyObject<int> obj;
MyDifferentObject<int> obj2;
foo(obj);
foo(obj2);
}
What about defining foo() using a template-template parameter?
template <template <typename...> class C, typename T>
void foo (C<T> & objct)
{
/...
}
or also
template <template <typename...> class C, typename T, typename ... Ts>
void foo (C<T, Ts...> & objct)
{
/...
}
to be more flexible and accept also type with multiple template types parameters.
This way, if you call
MyObject<int> obj;
MyDifferentObject obj2;
foo(obj);
foo(obj2);
you have that C is MyObject in first case, MyDifferentObject in the second case and T is int in both cases.
This, obviously, works only if the argument of foo() are object of a template class with only template type parameters so, for example, doesn't works for std::array
std::vector<int> v;
std::array<int, 5u> a;
foo(v); // compile: only types parameters for std::vector
foo(a); // compilation error: a non-type template parameter for std::array
I would like to create a variable in foo() whose type is the objct's generics, in this case, int.
Is there a way to do that?
If you can change the function signature, then you can do this:
template <typename T>
void foo(MyObject<T>& objct){
T variable;
If that is not an option, for example if you want foo to allow other templates too (such as in your edited question), then you can define a type trait:
template<class T>
struct fancy_type_trait
{
};
template<class T>
struct fancy_type_trait<MyObject<T>>
{
using type = T;
};
template<class T>
struct fancy_type_trait<MyDifferentObject<T>>
{
using type = T;
};
template <typename T>
void foo(T& objct){
using V = typename fancy_type_trait<T>::type;
V variable;
You can write a trait that determines the first template parameter of any instantiation of a template with one template parameter:
#include <type_traits>
template <typename T>
struct MyObject {};
template <typename T>
struct MyOtherObject {};
template <typename T>
struct first_template_parameter;
template <template<typename> typename T,typename X>
struct first_template_parameter< T<X> > {
using type = X;
};
int main() {
static_assert(std::is_same< first_template_parameter<MyObject<int>>::type,
first_template_parameter<MyOtherObject<int>>::type>::value );
}
The trait first_template_parameter can take any instantiation of a template with a single parameter and tells you what that parameter is. first_template_parameter< MyObject<int> >::type is int. More generally first_template_parameter< SomeTemplate<T> >::type is T (given that SomeTemplate has one parameter).
This is a slight generalization of the trait used in this answer and if needed it could be generalized to also work for instantiations of tempaltes with more than one parameter.
In your function you would use it like this:
template <typename T>
void foo(T& objct){
typename first_template_parameter<T>::type x;
}
Let's say I have a template function:
template <typename A, typename B>
A fancy_cast(B)
{
return {};
}
The intended usage is something like fancy_cast<int>(1.f).
But nothing stops the user from specifying the second template parameter manually: fancy_cast<int, int>(1.f), which would cause problems.
How can I prevent typename B from being specified and force it to be deduced?
I've come up with this:
// Using this wrapper prevents the code from being
// ill-formed NDR due to [temp.res]/8.3
template <auto V> inline constexpr auto constant_value = V;
template <
typename A,
typename ...Dummy,
typename B,
typename = std::enable_if_t<constant_value<sizeof...(Dummy)> == 0>
>
A fancy_cast(B)
{
return {};
}
It appears to work, but it's extremely cumbersome. Is there a better way?
What about making fancy_cast a variable template?
template <typename A>
struct fancy_cast_t {
template <typename B>
A operator()(B x) const { return x; }
};
template <typename A>
constexpr fancy_cast_t<A> fancy_cast {};
fancy_cast<int>(1.5); // works
fancy_cast<int, int>(1.5); // doesn't work
fancy_cast<int>.operator()<int>(1.5); // works, but no one would do this
This is not the most efficient solution, but you can create a class that has a template parameter for the type to convert to, and then have a constructor template that takes any type. Then if you add an operator T for the type you instantiate the class with you can have that return correct value. That would look like
template<typename T>
struct fancy_cast
{
T ret;
template<typename U>
fancy_cast(U u) : ret(u) {} // or whatever you want to do to convert U to T
operator T() && { return std::move(ret); }
};
int main()
{
double a = 0;
int b = fancy_cast<int>(a);
}
This works because there is no way to specify the template parameter for the constructor since you can't actually call it.
I found a good-looking solution.
We can use a non-type parameter pack, of a type that the user can't construct.1 E.g. a reference to a hidden class:
namespace impl
{
class require_deduction_helper
{
protected:
constexpr require_deduction_helper() {}
};
}
using require_deduction = impl::require_deduction_helper &;
template <typename A, require_deduction..., typename B>
A fancy_cast(B)
{
return {};
}
1 We do have to leave a loophole for constructing a deduction_barrier, otherwise the code would be ill-formed NDR. That's why the constructor is protected.
Yes.
Let's say I have a simple variadic struct that holds a typedef:
template<typename... TArgs> struct TupleTypeHolder {
using TupleType = std::tuple<TArgs*...>;
};
I want to pass TupleTypeHolder<something> as a template parameter to another class, and get that typedef.
All of my tries do not compile.
// None of these is valid
template<template<typename...> class TTupleTypeHolder> struct TupleMaker {
using MyTupleType = TTupleTypeHolder::TupleType; // Not valid
using MyTupleType = typename TTupleTypeHolder::TupleType; // Not valid
};
template<template<typename... A> class TTupleTypeHolder> struct TupleMaker2 {
// A is not a valid name here
using MyTupleType = TTupleTypeHolder<A...>::TupleType; // Not valid
using MyTupleType = typename TTupleTypeHolder<A...>::TupleType; // Not valid
};
Is there a way to use the variadic template parameters (in this case, TupleTypeHolder's TArgs...) of a variadic template class from a class that uses the aforementioned class as a template variadic template parameter?
Usage example:
template<typename... TArgs> struct TupleTypeHolder {
using TupleType = std::tuple<TArgs*...>;
};
template<typename... TArgs> static int getSomeValue() { ... }
template<??? T1, ??? T2> class TupleMaker
{
std::pair<int, int> someValues;
using TupleType1 = T1::TupleType;
using TupleType2 = T2::TupleType;
TupleMaker() : someValues{getSomeValue<T1's TArgs...>(),
getSomeValue<T2's TArgs...>()} { }
};
class MyTupleMaker : TupleMaker<TupleTypeHolder<int, char>,
TupleTypeHolder<int, float>>
{ };
MyTupleMaker::TupleType1 tuple1{new int(1), new char('a')};
MyTupleMaker::TupleType2 tuple1{new int(35), new float(12.f)};
Working usage example:
#include <tuple>
template<typename... TArgs> struct TupleTypeHolder {
using TupleType = std::tuple<TArgs*...>;
};
template<typename... TArgs> static int getSomeValue() { return 42; }
// primary template:
template<class T1, class T2>
struct TupleMaker;
// partial specialization:
template<template<class...> class TT1, template<class...> class TT2,
class... T1, class... T2>
struct TupleMaker < TT1<T1...>, TT2<T2...> >
{
std::pair<int, int> someValues;
using TupleType1 = typename TT1<T1...>::TupleType;
using TupleType2 = typename TT2<T2...>::TupleType;
TupleMaker() : someValues{getSomeValue<T1...>(),
getSomeValue<T2...>()} { }
};
struct MyTupleMaker : TupleMaker<TupleTypeHolder<int, char>,
TupleTypeHolder<int, float>>
{ };
MyTupleMaker::TupleType1 tuple1{new int(1), new char('a')};
MyTupleMaker::TupleType2 tuple2{new int(35), new float(12.f)};
int main() {}
The primary template takes two types, as you're passing types. TupleTypeHolder<int, char> is a type, a specialization of a template, not a template itself. Template template-parameters however take templates as arguments (not types), such as:
template<template<class...> class Foo>
struct Bar
{
using type = Foo<int, double, char>;
};
Bar< std::tuple > b; // note: no template arguments for `std::tuple`!
With partial specialization, you can split a template specialization into the template and the parameters, that's how the above works.
Template-template parameters are not really type parameters, are parameters to specify a template. That means what you pass through a template-template parameter is not a type, is a template:
template<template<typename> class TPARAM>
struct give_me_a_template
{
using param = TPARAM; //Error TPARAM is not a type, is a template.
using param_bool = TPARAM<bool>; //OK, thats a type
};
As you can see, the first alias is invalid, because TPARAM is not a type, is a template. But the second is a type (Is an instance of the template).
That said, examine your problem: What you called TupleTypeHolder could be viewed as a variadic-template typelist. So your goal is to make tuples of the types specified with typelists, right?
You can use partial specialization to extract the content of a typelist:
template<typename TupleTypeHolder>
struct tuple_maker;
template<typename... Ts>
struct tuple_maker<TupleTypeHolder<Ts...>>
{
using tuple_type = std::tuple<Ts...>;
};
An example of its usage could be:
using my_types = TupleTypeHolder<int,int,int>;
using my_tuple_type = typename tuple_maker<my_types>::tuple_type;
Of course this is not the exactly solution to your implementation, you need to extend the concept to multiple typelists (As your question showed). What I have provided is the guide to understand the problem and its solution.
I'm trying to use templates with multiple to pass data onto a function, but using only the first template argument as a filter. Something like this:
template <typename A, typename B>
class Component {
};
template <typename A>
class Context {
public:
void add(Component<A, void *> comp) {
}
}
typedef struct Foo { int x; } Foo;
typedef struct Bar { int y; } Bar;
Context<Foo> *context = new Context<Foo>();
Component<Foo, Bar> *comp = new Component<Foo, Bar>();
context->add(comp); // error
But the compiler complains that it cannot convert Component<Foo, Bar> to Component<Foo, void *>. Is there a way to accomplish this?
I think what you probably need to do is change the signature of the 'add' method:
template <typename A>
class Context
{
public:
template<class B>
void add(Component<A, B> comp)
{
}
};
However, I don't know the details of your problem so this is a mere guess.
I'm trying to use templates with multiple to pass data onto a function, but using only the first template argument as a filter. [...] But the compiler complains that it cannot convert Component to Component. Is there a way to accomplish this?
Well, your filter works doesn't it: your add function will only match a Component whose second template parameter is void*, and you're providing Bar. What else could you possibly expect? If you want it to handle other "second-parameters" as well, either remove the filter, provide an fallback function for it to match, or some kind of conversion.
Yes, add a converting copy constructor to your Component:
template<class U, class V>
Component(Component<U,V> const& other){
// ...
};
But that is still refineable with the appropriate enable_if SFINAE guard:
// <tr1/type_traits> for C++03
#include <type_traits> // for C++0x
template<class T, class U>
struct can_convert{
// std::tr1::... for C++03
static bool const value =
std::is_same<T,U>::value || std::is_convertible<T,U>::value;
};
template<class C1, class C2>
struct ice_and{
static bool const value = C1::value && C2::value;
}
// define for clarity and brevity
#define IF_CAN_CONVERT(A,B,U,V) \
typename std::enable_if<ice_and<can_convert<A,U>,can_convert<B,V> > >::type* = 0
template<class U, class V>
Component(Component<U,V> const& other, IF_CAN_CONVERT(A,B,U,V)){
// ...
};