I'm making a tensor class that can make tensors of any order. For example a third order 3 by 2 by 4 float tensor is made using Tensor<float, 3, 2, 4> a. The elements are recursively stored as arrays of lower order tensors, until the 1st order, at which point they're stored as an array of the given type.
template <typename T, int m, int ...n>
class Tensor {
public:
std::array<std::conditional<sizeof...(n) == 0, T, Tensor<T, n...>>, m> a;
};
I have a main loop to test it out.
#include <iostream>
#include <typeinfo>
int main() {
Tensor<float, 3, 2, 4> a;
// Tensor<float, 3> b;
std::cout << typeid(a.a[0]).name() << '\n';
return 0;
}
For some reason, constructing b fails but a succeeds. Also a.a[0] should simply be of type Tensor<float, 2, 4>, but instead it's some conditional type, at runtime. How is this even possible? Don't understand what's going on here, both why b fails with a succeeding (seems impossible) and why the types are conditionals at runtime.
Instantiating std::conditional<sizeof...(n) == 0, T, Tensor<T, n...>> instantiates Tensor<T, n...> even when the condition is true, meaning you give the template one argument instead of the 2+ it requires. Lazy arguments don't exist in C++ for general use (including standard library templates). You need to delay the instantiation of the problematic template so that you instantiate it only when applicable. I find the easiest way to do that is with if constexpr:
template<typename T>
static T type();
static auto determine_element_type() {
if constexpr (sizeof...(n) == 0) {
return type<T>();
} else {
return type<Tensor<T, n...>>();
}
}
std::array<decltype(determine_element_type()), m> a;
Your type of a was off because you used the std::conditional type itself instead of std::conditional_t or typename std::conditional<...>::type to get the resulting type.
std::conditional is a type trait. The way a type trait works, by convention, is that the result of the type-level computation is a member type alias of the std::conditional class, which otherwise is useless. I.e. std::conditional is basically defined like this:
template<bool, typename, typename>
struct conditional;
template<typename T, typename F>
struct conditional<true, T, F> { using type = T; };
template<typename T, typename F>
struct conditional<false, T, F> { using type = F; };
There are also template aliases to reduce typing:
template<bool B, typename T, typename F>
using conditional_t = typename std::conditional<B, T, F>::type;
And you're meant to use it like this
template <typename T, int m, int ...n>
struct Tensor {
std::array<std::conditional_t<sizeof...(n) == 0, T, Tensor<T, n...>>, m> a;
};
This doesn't actually work: if you try to instantiate Tensor<float, 3>, then that tries to instantiate Tensor<float>, and that fails, since Tensor must have at least one int. It doesn't matter that the use of Tensor<float> is under std::conditional_t. std::conditional_t is not like a ? : conditional. It is like a function
Type conditional_t(bool b, Type t, Type f) { return b ? t : f; }
The "arguments" are evaluated before the conditional switches between them. If one of the arguments is ill-formed, the conditional will not save you.
A better design would be specialization of the whole class.
template<typename T, int... ns>
struct tensor;
// a rank-0 tensor is a scalar
template<typename T>
struct tensor<T> {
T value;
};
// a rank-(n+1) tensor is some number of rank-n tensors
template<typename T, int n, int... ns>
struct tensor<T, n, ns...> {
std::array<tensor<T, ns...>, n> value;
};
Not sure what you meant by Tensor<float, 3, 2, 4> working in your version; didn't work for me!
Here's a neat, complete example
You can define the tensor as nested arrays like this:
#include <array>
#include <type_traits>
namespace detail {
template <class T, std::size_t n, std::size_t... ns>
auto helper() {
if constexpr (sizeof...(ns) == 0)
return std::array<T, n>();
else
return std::array<decltype(helper<T, ns...>()), n>();
}
}
template <class T, std::size_t n, std::size_t... ns>
using Tensor = decltype(detail::helper<T, n, ns...>());
int main()
{
Tensor<float, 2, 3, 4> b;
b[0][1][2] = 1;
}
Related
In the tutorial is exersize, where need:
add error checking to the binary template causes a compilation error if N contains digits other than 0 or 1
It is enough simple to do it by standard features:
template <int N>
struct Binary {
static_assert(N == 0 || N == 1, "binary have to be 0 or 1");
const int n = N;
};
But how to do it by mpl::vector_c? For example:
using values = mpl::vector_c<int, 0, 1>;
template<int N,
typename = typename std::enable_if</*TODO compare N with values from mpl::vector_c*/>::type>
struct Binary {
const int n = N;
};
Partial template specialization might help you here:
The trick is to first define the template the way you use it (with an int (N) and the type (which will be the mpl::vector_c), and to then 'break it down' into the components you want to obtain access to (in this case two integers of which the vector_c is comprised). Below you can find the example which works for a vector_c with exactly two parameters. You can also extend this to work with an arbitrary amount of parameters (which might be a fun second exercise :) and could be achieved using a parameter pack).
#include <boost/mpl/vector_c.hpp>
using namespace boost;
using values = mpl::vector_c<int, 1, 1>;
template <int N, typename T> struct Binary;
template <int N, typename t, long a, long b> struct Binary<N, boost::mpl::vector_c<t, a, b>> {
static_assert(N == a && b == N, "invalid vector_c");
};
int main() {
//Binary<1, boost::mpl::vector_c<int, 1, 2>> koo; // commented out as assertion fails
Binary<1, values> kooo;
}
Related Links you might find interesting:
Partial Template Specializatoin on Cppreference
a more or less generic way of accessing template parameters in this case from an array
Note that this solution does not work with standards <= c++03
Building on top of the other answer,
below you can find a solution using SFINAE as indicated in the question by the use of enable_if.
This is somewhat more tricky than I originally expected it to be, because somehow the amount of provided arguments for the boost::mpl_vector_c in the partial template specialization does not match the size of the mpl::vector.
Therefore, I define a helper struct below which allows to perform the boolean 'and' operation on a subset of a provided variadic template of boolean values.
If c++17 is available, the lines marked by the comment c++14/c++17 can be excanged.
If c++14 is not available, the the index_sequence can for instance be replaced by making use of a recursive #value declaraion in the struct and _v/_t suffixes replaced by [...]::value/type respectively.
#include <boost/mpl/vector_c.hpp>
#include <tuple>
/// Helper struct which enables to evaluate a conjunction of a subset of a set of booleans.
template <typename IndexSequence, bool... v> struct PartialConjunction;
/// Parital template specialization
template <std::size_t... idx, bool... b>
struct PartialConjunction<std::index_sequence<idx...>, b...>
: std::integral_constant<
bool, (std::get<idx>(std::forward_as_tuple(b...)) && ...)> {};
/// 'Alias' for the value
template <std::size_t S, bool... v> constexpr auto PartialConjunction_v =
PartialConjunction<decltype(std::make_index_sequence<S>()), v...>::value;
/// Actual struct which holds the type of the vector in ::type if it meets the criterion
template <typename VecType, VecType N, typename MplVector> struct Same; //< c++14
//template <auto N, typename MplVector> struct Same; //< c++17
template <typename VecType, VecType N, long... a> struct Same<VecType, N, boost::mpl::vector_c<VecType, a...>> {// c++14
//template <typename VecType, VecType N, long... a> struct Same<N, boost::mpl::vector_c<VecType, a...>> { // c++17
using type = boost::mpl::vector_c<VecType, a...>;
static constexpr auto Size = typename type::size();
static constexpr auto value = PartialConjunction_v<Size, N == static_cast<VecType>(a)...>;
};
/// Alias for the type which performs SFINAE.
template <typename T, T N, typename VectorType, typename = std::enable_if_t<Same<T, N, VectorType>::value>> // c++14..
using Same_t = typename Same<T, N, VectorType>::type;
//template <auto N, typename VectorType, typename = std::enable_if_t<Same<N, VectorType>::value>> // c++17..
//using Same_t = typename Same<N, VectorType>::type;
int main() {
// For the c++17 version, the first 'int' in the parameter list can be omitted
//Same_t<int, 1, boost::mpl::vector_c<int, 1, 1, 2>> fails;
Same_t<int, 1, boost::mpl::vector_c<int, 1, 1, 1>> ok;
Same_t<int, 1, boost::mpl::vector_c<int, 1, 1>> okok;
Same_t<int, 1, boost::mpl::vector_c<int, 1>> okokok;
}
The code example can be found here in CompilerExplorer.
I have a set of classes, like A, B, C and a tuple of tuples containing these classes, like this:
struct A {
std::string name{"a"};
};
struct B {
std::string name{"b"};
};
struct C {
std::string name{"c"};
};
// only first items A(), B(), C() do matter, other are arbitrary
auto t = std::make_tuple(
std::make_tuple(A(), 1, 2, 3),
std::make_tuple(B(), 4, 5, 6),
std::make_tuple(C(), 7, 8)
);
My target logic is to select a tuple from a container tuple by match of type of the first element. So, by example above, I want to get string 'b', when calling something like this:
std::string the_b = having_first_of_type<B, decltype(t)>::get().name;
I am trying to get a solution with templates:
// a template for getting first item from N-th tuple int Tuples
template <std::size_t N, typename... Tuples>
using first_of_nth = std::tuple_element<0, std::tuple_element<N, std::tuple<Tuples...>>>;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<N,
typename std::enable_if<std::is_same<T, typename first_of_nth<N, Tuples...>::type>::value, T>::type* = nullptr>
{
static auto& get(const std::tuple<Tuples...>& tuple) {
return std::get<N>(tuple);
}
};
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<N,
typename std::enable_if<!std::is_same<T, typename first_of_nth<N, Tuples...>::type>::value, T>::type* = nullptr> : having_first_of_type<N-1, T, Tuples...>;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<0, T, Tuples...> {}
And I can't form the specializations in the right way. For the first one (std::is_same is true) compiler says: error: expected '>' for position of '= nullptr'. It looks like it does not accept default value for T*, but I am confused why..
What's the error? Or, perhaps there is a better way to get what I want?
UPDATE
below are 2 working solutions: from N. Shead and #n314159 - thank you!
I forgot to mention that I tried to get it using C++14, but the solutions are for C++17.
C++17 is also OK.
Assigning a nullptr where a tyle belongs does not make sense. You should remove that. Further I am not exactly sure, what goes wrong. We can make the whole thing a bit easier by using the std::get version templated on a type not an index, then we don't have to carry the N:
#include <tuple>
#include <type_traits>
#include <iostream>
template<class T, class Tup, bool negated = false>
using first_of = std::enable_if_t<negated ^ std::is_same_v<std::tuple_element_t<0, Tup>, T>>;
template<class T, class= void, class... Tups>
struct first_match_impl;
template<class T, class Tup1, class... Tups>
struct first_match_impl<T, first_of<T, Tup1>, Tup1, Tups...> {
using type = Tup1;
template<class FullTup>
static Tup1& get(FullTup& t) {
return std::get<Tup1>(t);
}
};
template<class T, class Tup1, class... Tups>
struct first_match_impl<T, first_of<T, Tup1, true>, Tup1, Tups...>: first_match_impl<T, void, Tups...> {};
template<class T, class... Tups>
using first_match = first_match_impl<T, void, Tups...>;
template<class T, class... Tups>
auto& get_first_of(std::tuple<Tups...> &t) {
return first_match<T, Tups...>::get(t);
}
int main()
{
std::tuple<std::tuple<int, float>, std::tuple<char, double>> t {{1,2.}, {'A', 4.}};
std::cout << std::get<0>(get_first_of<char>(t)); // prints A
}
Note that this will not compile when you have two exactly identicall tuples in your tuple but will compile if there are different tuples with the same first element (then it will pick the first of them).
EDIT: This inspired me write a small library providing iterator like support for tuples. See here.
You've tried to give a default value in a place where the compiler expects a concrete type.
I'm assuming you want to get the whole inner tuple?
In that case, my attempt at solving this would look something like this:
template <typename T, typename Tuple>
constexpr bool tuple_first_type_is() {
if constexpr (std::tuple_size_v<Tuple> == 0)
return false;
else
return std::is_same_v<T, std::tuple_element_t<0, Tuple>>;
}
template <typename T, std::size_t I, typename NestedTuple>
constexpr decltype(auto) having_first_of_type_impl(NestedTuple&& nested_tuple) noexcept {
using D = std::decay_t<NestedTuple>;
static_assert(I < std::tuple_size_v<D>, "type not found in tuple");
using ith_tuple = std::tuple_element_t<I, D>;
if constexpr (tuple_first_type_is<T, ith_tuple>())
return std::get<I>(std::forward<NestedTuple>(nested_tuple));
else
return having_first_of_type_impl<T, I+1>(std::forward<NestedTuple>(nested_tuple));
}
template <typename T, typename NestedTuple>
constexpr decltype(auto) having_first_of_type(NestedTuple&& nested_tuple) noexcept {
static_assert(std::tuple_size_v<std::decay_t<NestedTuple>> > 0, "empty tuple");
return having_first_of_type_impl<T, 0>(std::forward<NestedTuple>(nested_tuple));
}
Live: http://coliru.stacked-crooked.com/a/aa1637939a5d7d7c
I'm not 100% confident I've done everything correctly with value categories and the like, and there could well be a better way of going about this, but this is the sort of thing I would start off with.
Is there any syntax through which I can define the dimensionality of a static array at compile time? Let's assume I have a tuple of size D, with values d_0,...,d_{D-1}. I would like to be able to create an array T arr[d_0][d_1]...[d_{D-1}]. Is there any way to achieve this at compile time? I am specifically asking about static array syntax, not about how to nest structs.
Here's a code snippet to clarify what I want to achieve:
template<typename T, template<typename, auto> typename Container, auto DimC, auto...Dim>
struct NestedContainer
{
using type = Container<typename NestedContainer<T, Container, Dim...>::type, DimC>;
};
template<typename T, template<typename, auto> typename Container, auto Dim>
struct NestedContainer<T, Container, Dim>
{
using type = Container<T, Dim>;
};
template<typename T, int D>
struct Arr
{
T e[D];
T& operator[](int i) { return e[i]; }
};
template<typename T, int D, int...Dim>
struct MultiArr
{
using multi_arr = typename NestedContainer<T, Arr, Dim...>::type;
multi_arr e[D];
auto& operator[](int i) { return e[i]; }
};
Used like so:
MultiArr<float, 3, 3, 3> a;
a[2][2][2] = 3;
I am curious whether this is achievable through some form of syntax without nesting classes.
It can easily be done using template metaprogramming recursion. The only tricky thing is that you need to apply the extents in the correct order. Note that int[x][y] is (conceptually) (int[y])[x].
#include <cstddef>
#include <type_traits>
template <typename E, size_t... extents>
struct MultiArrHelper;
template <typename E>
struct MultiArrHelper<E> {
using type = E;
};
template <typename E, size_t extent, size_t... extents>
struct MultiArrHelper<E, extent, extents...> {
using type = typename MultiArrHelper<E, extents...>::type[extent];
};
template <typename E, size_t... extents>
using MultiArr = typename MultiArrHelper<E, extents...>::type;
int main() {
MultiArr<int, 2, 2, 3> a;
static_assert(std::is_same<decltype(a), int[2][2][3]>::value);
}
http://coliru.stacked-crooked.com/a/6b89020318e78b90
Consider the following code sample:
#include <iostream>
#include <tuple>
template<typename T, std::size_t Rank, std::size_t... In>
struct help;
template<typename T, std::size_t... In>
struct help<T, 1, In...>
{
static constexpr auto a = std::make_tuple(In...);
T data[std::get<0>(a)];
};
template<typename T, std::size_t... In>
struct help<T, 2, In...>
{
static constexpr auto a = std::make_tuple(In...);
T data[std::get<0>(a)][std::get<1>(a)];
};
template<typename T, std::size_t... In>
class foo : public help<T, sizeof...(In), In...>
{
private:
using base = help<T, sizeof...(In), In...>;
public:
template<typename... Tn>
constexpr foo(Tn&&... args)
: base{ { args... } } // constructor would only work if rank == 1
{}
T operator[](std::size_t i) noexcept { return base::data[i]; }
constexpr T operator[](std::size_t i) const noexcept { return base::data[i]; }
};
int main()
{
foo<int, 6> a = { 1, 2, 3, 4, 5, 6 };
for (std::size_t i = 0; i < 6; ++i) {
std::cout << a[i] << " ";
}
}
This is as far as I got.
I'm trying to make a class, objects of which shall be constructed as foo<int, 2>; foo<int, 3, 4>; foo<int, 1, 2, 3, 4>; These objects will hold an array member respectively of type int[2]; int[3][4]; int[1][2][3][4].
My initial thinking was to make a helper template class which would specialize quite a lot (at least until an array of rank 20). That is obviously verbose, and after I get to an array of rank 2, I have no idea what the constructor for foo should be.
The constructor problem can be solved by making the class a POD (no access specifiers, etc) and construct via aggregate initialization (much like std::array does), but that goes against my work so far with the "proper inherited specialization".
I would appreciate some ideas. How would you go about doing something like this?
I think you're overcomplicating things. Just make a type trait that gives you the correct std::array type:
template <class T, size_t... Is>
struct multi_array;
template <class T, size_t... Is>
using multi_array_t = typename multi_array<T, Is...>::type;
With zero dimensions, you just get the type:
template <class T>
struct multi_array<T> {
using type = T;
};
and with at least one dimension, you just wrap it in std::array and recurse:
template <class T, size_t I, size_t... Is>
struct multi_array<T, I, Is...> {
using type = std::array<multi_array_t<T, Is...>, I>;
};
So you get something like:
static_assert(std::is_same<multi_array_t<int, 2>, std::array<int, 2>>::value, "!");
static_assert(std::is_same<multi_array_t<int, 3, 4>, std::array<std::array<int, 4>, 3> >::value, "!");
Suppose I have an std::function that takes as input N arguments of type T (this can be constructed using some metaprogramming magic; see below), where N is a template parameter. I would like to std::bind the first argument to construct a function with N-1 arguments (e.g. myBind<...>(someValue)). I could not think of a clever metaprogramming trick to do this. Any suggestions?
From Lambda function with number of arguments determined at compile-time:
You can write a template n_ary_function with a nested typedef type. This type can be used as follows:
template <int N> class A {
typename n_ary_function<N, double>::type func;
};
Following the definition of n_ary_function:
template <std::size_t N, typename Type, typename ...Types>
struct n_ary_function {
using type = typename n_ary_function<N - 1, Type, Type, Types...>::type;
};
template <typename Type, typename ...Types>
struct n_ary_function<0, Type, Types...> {
using type = std::function<void(Types...)>;
};
std::bind uses std::is_placeholder to detect placeholders, which means that you can write your own placeholders to use with std::bind by partially specializing std::is_placeholder:
template<int N>
struct my_placeholder { static my_placeholder ph; };
template<int N>
my_placeholder<N> my_placeholder<N>::ph;
namespace std {
template<int N>
struct is_placeholder<::my_placeholder<N>> : std::integral_constant<int, N> { };
}
This makes it possible to get a placeholder from an integer. The rest is simply the standard integer sequence trick:
template<class R, class T, class...Types, class U, int... indices>
std::function<R (Types...)> bind_first(std::function<R (T, Types...)> f, U val, std::integer_sequence<int, indices...> /*seq*/) {
return std::bind(f, val, my_placeholder<indices+1>::ph...);
}
template<class R, class T, class...Types, class U>
std::function<R (Types...)> bind_first(std::function<R (T, Types...)> f, U val) {
return bind_first(f, val, std::make_integer_sequence<int, sizeof...(Types)>());
}
Demo. std::integer_sequence is technically C++14, but it's easily implementable in C++11 - just search on SO.
#include <functional>
#include <cstddef>
#include <utility>
#include <tuple>
template <std::size_t N, typename Type, typename... Types>
struct n_ary_function
{
using type = typename n_ary_function<N - 1, Type, Type, Types...>::type;
};
template <typename Type, typename... Types>
struct n_ary_function<0, Type, Types...>
{
using type = std::function<void(Types...)>;
};
using placeholders_list = std::tuple<decltype(std::placeholders::_1)
, decltype(std::placeholders::_2)
, decltype(std::placeholders::_3)
, decltype(std::placeholders::_4)
, decltype(std::placeholders::_5)
, decltype(std::placeholders::_6)
, decltype(std::placeholders::_7)
, decltype(std::placeholders::_8)
, decltype(std::placeholders::_9)
, decltype(std::placeholders::_10)
>;
template <typename F>
struct arity;
template <typename R, typename... Args>
struct arity<std::function<R(Args...)>>
{
static constexpr std::size_t value = sizeof...(Args);
};
template <typename F, typename T, std::size_t... Ints>
auto binder(F f, T t, std::index_sequence<Ints...>)
{
return std::bind(f, t,
typename std::tuple_element<Ints, placeholders_list>::type{}...);
}
template <typename F, typename T>
auto myBind(F f, T t)
{
return binder(f, t, std::make_index_sequence<arity<F>::value - 1>{});
}
Tests:
#include <iostream>
void foo(int a, int b, int c, int d, int e)
{
std::cout << a << b << c << d << e << std::endl;
}
int main()
{
n_ary_function<5, int>::type f = foo;
n_ary_function<4, int>::type b = myBind(f, 1);
b(2, 3, 4, 5);
}
DEMO