I have a function that computes a certain object from a given parameter (say, an important node from a graph). Now, when calculating such an object, the function might allocate some memory. Sometimes I want the function to just return the result, and sometimes to return the result plus the memory used to compute it.
I typically solve this binary case like this:
enum class what {
what1, // return, e.g., just an int
what2 // return, e.g., a std::pair<int, std::vector<int>>
};
template <what w>
std::conditional_t<w == what::what1, int, std::pair<int, std::vector<int>>>
calculate_something(const param& p) { ... }
I would like to generalize the solution above to longer enumerations
enum class list_whats {
what1,
what2,
what3,
what4,
what5
};
One possible solution is to nest as many std::conditional_t as needed
template <list_whats what>
std::conditional_t<
what == list_whats::what1,
int,
std::conditional_t<
what == list_whats::what2,
float,
....
>
>
>
calculate_something(const param& p) { ... }
But this is cumbersome and perhaps not too elegant.
Does anyone know how to do this in C++ 17?
EDIT
To make the question perfectly clear: how do I implement the function return_something so as to be able to run the following main?
int main() {
int s1 = return_something<list_whats::what1>();
s1 = 3;
float s2 = return_something<list_whats::what2>();
s2 = 4.0f;
double s3 = return_something<list_whats::what3>();
s3 = 9.0;
std::string s4 = return_something<list_whats::what4>();
s4 = "qwer";
std::vector<int> s5 = return_something<list_whats::what5>();
s5[3] = 25;
}
I don't think you should use std::conditional at all to solve your problem. If I get this right, you want to use a template parameter to tell your function what to return. The elegant way to do this could look something like this:
#include <vector>
enum class what { what1, what2 };
template <what W>
auto compute() {
if constexpr (W == what::what1) {
return 100;
}
if constexpr (W == what::what2) {
return std::pair{100, std::vector<int>{}};
}
}
auto main() -> int {
[[maybe_unused]] const auto as_int = compute<what::what1>();
[[maybe_unused]] const auto as_pair = compute<what::what2>();
}
You can also use template specialization if you prefer another syntax:
template <what W>
auto compute();
template <>
auto compute<what::what1>() {
return 100;
}
template <>
auto compute<what::what2>() {
return std::pair{100, std::vector<int>{}};
}
Here's my approach:
what_pair is a pair that corresponds one enum to one type.
what_type_index accepts a enum and a std::tuple<what_pair<...>...> and searches the tuple map where the enums are equal and returns index. It returns maximum std::size_t value, if no match was found.
what_type is the final type, it is the tuple element at the found position. The program won't compile when the index is std::size_t max value because of invalid std::tuple access.
template<what W, typename T>
struct what_pair {
constexpr static what w = W;
using type = T;
};
template<what w, typename tuple_map>
constexpr auto what_type_index() {
std::size_t index = std::numeric_limits<std::size_t>::max();
auto search_map = [&]<std::size_t... Ints>(std::index_sequence<Ints...>) {
((std::tuple_element_t<Ints, tuple_map>::w == w ? (index = Ints) : 0), ...);
};
search_map(std::make_index_sequence<std::tuple_size_v<tuple_map>>());
return index;
}
template<what w, typename tuple_map>
using what_type = typename
std::tuple_element_t<what_type_index<w, tuple_map>(), tuple_map>::type;
and this is the example usage:
int main() {
using what_map = std::tuple<
what_pair<what::what1, int>,
what_pair<what::what2, float>,
what_pair<what::what3, double>,
what_pair<what::what4, std::string>,
what_pair<what::what5, std::vector<int>>>;
static_assert(std::is_same_v<what_type<what::what1, what_map>, int>);
static_assert(std::is_same_v<what_type<what::what2, what_map>, float>);
static_assert(std::is_same_v<what_type<what::what3, what_map>, double>);
static_assert(std::is_same_v<what_type<what::what4, what_map>, std::string>);
static_assert(std::is_same_v<what_type<what::what5, what_map>, std::vector<int>>);
//compilation error, because 'what6' wasn't specified in the 'what_map'
using error = what_type<what::what6, what_map>;
}
try it out on godbolt.
I found a possible solution that nests two structs: the first takes a list of Boolean values to indicate which type should be used, and the nested struct takes the list of possible types (see conditional_list in the example code below -- the nested structs were inspired by this answer). But perhaps it's not elegant enough. I'm wondering if there is a possible solution of the form
std::conditional_list<
..., // list of Boolean values, (of any length!)
... // list of types (list that should be as long as the first)
>::type
My proposal
#include <type_traits>
#include <iostream>
#include <vector>
// -----------------------------------------------------------------------------
template<auto A, auto... ARGS>
constexpr auto sum = A + sum<ARGS...>;
template<auto A>
constexpr auto sum<A> = A;
// -----------------------------------------------------------------------------
template <bool... values>
static constexpr bool exactly_one_v = sum<values...> == 1;
// -----------------------------------------------------------------------------
template <bool... values>
struct which {
static_assert(exactly_one_v<values...>);
template <std::size_t idx, bool v1, bool... vs>
struct _which_impl {
static constexpr std::size_t value =
(v1 ? idx : _which_impl<idx + 1, vs...>::value);
};
template <std::size_t idx, bool v>
struct _which_impl<idx, v> {
static constexpr std::size_t value = (v ? idx : idx + 1);
};
static constexpr std::size_t value = _which_impl<0, values...>::value;
};
template <bool... conds>
static constexpr std::size_t which_v = which<conds...>::value;
// -----------------------------------------------------------------------------
template <std::size_t ith_idx, typename... Ts>
struct ith_type {
template <std::size_t cur_idx, typename t1, typename... ts>
struct _ith_type_impl {
typedef
std::conditional_t<
ith_idx == cur_idx,
t1,
typename _ith_type_impl<cur_idx + 1, ts...>::type
>
type;
};
template <std::size_t cur_idx, typename t1>
struct _ith_type_impl<cur_idx, t1> {
typedef
std::conditional_t<ith_idx == cur_idx, t1, std::nullptr_t>
type;
};
static_assert(ith_idx < sizeof...(Ts));
typedef typename _ith_type_impl<0, Ts...>::type type;
};
template <std::size_t ith_idx, typename... ts>
using ith_type_t = typename ith_type<ith_idx, ts...>::type;
// -----------------------------------------------------------------------------
template <bool... conds>
struct conditional_list {
template <typename... ts>
struct good_type {
static_assert(sizeof...(conds) == sizeof...(ts));
typedef ith_type_t<which_v<conds...>, ts...> type;
};
};
// -----------------------------------------------------------------------------
enum class list_whats {
what1,
what2,
what3,
what4,
what5,
};
template <list_whats what>
typename conditional_list<
what == list_whats::what1,
what == list_whats::what2,
what == list_whats::what3,
what == list_whats::what4,
what == list_whats::what5
>::template good_type<
int,
float,
double,
std::string,
std::vector<int>
>::type
return_something() noexcept {
if constexpr (what == list_whats::what1) { return 1; }
if constexpr (what == list_whats::what2) { return 2.0f; }
if constexpr (what == list_whats::what3) { return 3.0; }
if constexpr (what == list_whats::what4) { return "42"; }
if constexpr (what == list_whats::what5) { return {1,2,3,4,5}; }
}
int main() {
auto s1 = return_something<list_whats::what1>();
s1 = 3;
auto s2 = return_something<list_whats::what2>();
s2 = 4.0f;
auto s3 = return_something<list_whats::what3>();
s3 = 9.0;
auto s4 = return_something<list_whats::what4>();
s4 = "qwer";
auto s5 = return_something<list_whats::what5>();
s5[3] = 25;
}
An alternative to working only with types is to write a function that returns the specific type, or an identity<type>. It's sometimes more readable. Here is an example:
// if you don't have it in std
template<typename T>
struct identity {
using type = T;
};
enum class what {
what1,
what2,
what3
};
template<what w>
auto return_type_for_calc() {
if constexpr (w == what::what1) {
return identity<int>();
} else if constexpr (w==what::what2) {
return identity<double>();
} else {
return identity<float>();
}
}
template<what w>
decltype(return_type_for_calc<w>())
calculate_something()
{
return {};
}
int main() {
calculate_something<what::what1>();
calculate_something<what::what2>();
return 0;
}
Although I already posted an answer to my own question, and I accepted an answer from another user, I thought I could post another possibility in tackling this problem using the following struct
template <typename... Ts> struct type_sequence { };
which I learnt about in this talk by Andrei Alexandrescu. Since I learnt quite a bit by using it and the result is a bit simpler than the original answer that used two nested structs I thought I would share it here. However, the solution I would actually implement is the one that was accepted.
This is the full code with a main function included. Notice the change in the specification of function return_something. Now this function indicates the return type (which I like very much, perhaps I'm old fashioned) in a more readable way than in my first answer. You can try it out here.
#include <type_traits>
#include <iostream>
#include <vector>
template <bool... values>
struct which {
template <std::size_t idx, bool v1, bool... vs>
struct _which_impl {
static constexpr std::size_t value =
(v1 ? idx : _which_impl<idx + 1, vs...>::value);
};
template <std::size_t idx, bool v>
struct _which_impl<idx, v> {
static constexpr std::size_t value = (v ? idx : idx + 1);
};
static constexpr std::size_t value = _which_impl<0, values...>::value;
};
template <std::size_t ith_idx, typename... Ts>
struct ith_type {
template <std::size_t cur_idx, typename t1, typename... ts>
struct _ith_type_impl {
using type =
std::conditional_t<
ith_idx == cur_idx,
t1,
typename _ith_type_impl<cur_idx + 1, ts...>::type
>;
};
template <std::size_t cur_idx, typename t1>
struct _ith_type_impl<cur_idx, t1> {
using type = std::conditional_t<ith_idx == cur_idx, t1, std::nullptr_t>;
};
using type = typename _ith_type_impl<0, Ts...>::type;
};
template <std::size_t ith_idx, typename... ts>
using ith_type_t = typename ith_type<ith_idx, ts...>::type;
template <bool... conds>
static constexpr std::size_t which_v = which<conds...>::value;
template <typename... Ts> struct type_sequence { };
template <bool... values> struct bool_sequence {
static constexpr std::size_t which = which_v<values...>;
};
template <std::size_t ith_idx, typename... Ts>
struct ith_type<ith_idx, type_sequence<Ts...>> : ith_type<ith_idx, Ts...>
{ };
template <typename bool_sequence, typename type_sequence>
struct conditional_list {
using type = ith_type_t<bool_sequence::which, type_sequence>;
};
template <typename bool_sequence, typename type_sequence>
using conditional_list_t =
typename conditional_list<bool_sequence, type_sequence>::type;
enum class list_whats {
what1,
what2,
what3,
what4,
what5,
};
template <list_whats what>
conditional_list_t<
bool_sequence<
what == list_whats::what1,
what == list_whats::what2,
what == list_whats::what3,
what == list_whats::what4,
what == list_whats::what5
>,
type_sequence<
int,
float,
double,
std::string,
std::vector<int>
>
>
return_something() noexcept {
if constexpr (what == list_whats::what1) { return 1; }
if constexpr (what == list_whats::what2) { return 2.0f; }
if constexpr (what == list_whats::what3) { return 3.0; }
if constexpr (what == list_whats::what4) { return "42"; }
if constexpr (what == list_whats::what5) { return {1,2,3,4,5}; }
}
int main() {
[[maybe_unused]] auto s1 = return_something<list_whats::what1>();
[[maybe_unused]] auto s2 = return_something<list_whats::what2>();
[[maybe_unused]] auto s3 = return_something<list_whats::what3>();
[[maybe_unused]] auto s4 = return_something<list_whats::what4>();
[[maybe_unused]] auto s5 = return_something<list_whats::what5>();
}
Related
I've got a parameter pack saved as a tuple in some function traits struct.
How can I find out, how many of those parameters are std::optional types?
I tried to write a function to check each argument with a fold expression, but this doesn't work as I only pass a single template type which is the tuple itself.
void foo1(){}
void foo2(int,float){}
void foo3(int, std::optional<int>, float, std::optional<int>){}
void foo4(int, std::optional<int>, bool){}
template<typename R, typename... TArgs>
struct ftraits<R(TArgs...)>
{
using ret = R;
using args = std::tuple<TArgs...>;
};
template<typename T>
struct is_optional : std::false_type
{
};
template<typename T>
struct is_optional<std::optional<T>> : std::true_type
{
};
template<typename... Ts>
constexpr auto optional_count() -> std::size_t
{
// doesn't work since Ts is a single parameter with std::tuple<...>
return (0 + ... + (is_optional<Ts>::value ? 1 : 0));
}
int main() {
using t1 = typename ftraits<decltype(foo1)>::args;
std::cout << optional_count<t1>() << std::endl; // should print 0
using t2 = typename ftraits<decltype(foo2)>::args;
std::cout << optional_count<t2>() << std::endl; // should print 0
using t3 = typename ftraits<decltype(foo3)>::args;
std::cout << optional_count<t3>() << std::endl; // should print 2
using t4 = typename ftraits<decltype(foo4)>::args;
std::cout << optional_count<t4>() << std::endl; // should print 1
}
You can use template partial specialization to get element types of the tuple and reuse the fold-expression
template<typename>
struct optional_count_impl;
template<typename... Ts>
struct optional_count_impl<std::tuple<Ts...>> {
constexpr static std::size_t count =
(0 + ... + (is_optional<Ts>::value ? 1 : 0));
};
template<typename Tuple>
constexpr auto optional_count() -> std::size_t {
return optional_count_impl<Tuple>::count;
}
Demo
You can get the size of the tuple using std::tuple_size, then iterate over all its members using a recursive template. In that template, you can pretend to construct an instance of the tuple using std::declval, get the value at the current index using std::get, and then finally get the type of that value using decltype.
Example implementation:
#include <optional>
#include <tuple>
#include <utility>
template<typename T>
struct is_optional : std::false_type {};
template<typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template<typename Tuple, size_t i>
constexpr size_t optional_count_impl() {
size_t val = is_optional<std::remove_reference_t<decltype(std::get<i>(std::declval<Tuple>()))>>::value ? 1 : 0;
if constexpr (i) {
val += optional_count_impl<Tuple, i - 1>();
}
return val;
}
template<typename Tuple>
constexpr size_t optional_count() {
const size_t tuple_size = std::tuple_size<Tuple>::value;
if constexpr (tuple_size == 0) {
return 0;
} else {
return optional_count_impl<Tuple, tuple_size - 1>();
}
}
using Tup1 = std::tuple<int, int, std::optional<size_t>, bool, std::optional<bool>, std::optional<std::optional<int>>>;
int main() {
return optional_count<Tup1>();
}
I am struggling with an implementation of the Cartesian product for
multiple indices with a given range 0,...,n-1.
The basic idea is to have a function:
cartesian_product<std::size_t range, std::size_t sets>()
with an output array that contains tuples that hold the different products
[(0,..,0), (0,...,1), (0,...,n-1),...., (n-1, ..., n-1)]
An simple example would be the following:
auto result = cartesian_product<3, 2>();
with the output type std::array<std::tuple<int, int>, (3^2)>:
[(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]
My main problem is that my version of the Cartesian product is slow and creates a stack overflow if you choose to have more than 5 sets. I believe that my code has too many recursions and temporary variables.
My implementation (C++17) can be found here: cartesian_product
#include <stdio.h>
#include <iostream>
#include <tuple>
template<typename T, std::size_t ...is>
constexpr auto flatten_tuple_i(T tuple, std::index_sequence<is...>) {
return std::tuple_cat(std::get<is>(tuple)...);
}
template<typename T>
constexpr auto flatten_tuple(T tuple) {
return flatten_tuple_i(tuple, std::make_index_sequence<std::tuple_size<T>::value>{});
}
template<std::size_t depth, typename T>
constexpr auto recursive_flatten_tuple(T tuple){
if constexpr(depth <= 1){
return tuple;
}else{
return recursive_flatten_tuple<depth-1>(flatten_tuple(tuple));
}
}
template<std::size_t depth, typename T, std::size_t ...is>
constexpr auto wdh(T&& tuple, std::index_sequence<is...>){
if constexpr (depth == 0) {
return tuple;
}else{
//return (wdh<depth-1>(std::tuple_cat(tuple, std::make_tuple(is)),std::make_index_sequence<sizeof...(is)>{})...);
return std::make_tuple(wdh<depth-1>(std::tuple_cat(tuple, std::make_tuple(is)), std::make_index_sequence<sizeof...(is)>{})...);
}
}
template<std::size_t sets, typename T, std::size_t ...is>
constexpr auto to_array(T tuple, std::index_sequence<is...>){
if constexpr (sets == 0){
auto t = (std::make_tuple(std::get<is>(tuple)),...);
std::array<decltype(t), sizeof...(is)> arr = {std::make_tuple(std::get<is>(tuple))...};
//decltype(arr)::foo = 1;
return arr;
}else{
auto t = ((std::get<is>(tuple)),...);
std::array<decltype(t), sizeof...(is)> arr = {std::get<is>(tuple)...};
return arr;
}
}
template<std::size_t sets, std::size_t ...is>
constexpr auto ct_i(std::index_sequence<is...>){
if constexpr (sets == 0){
auto u = std::tuple_cat(wdh<sets>(std::make_tuple(is), std::make_index_sequence<sizeof...(is)>{})...);
auto arr = to_array<sets>(u, std::make_index_sequence<std::tuple_size<decltype(u)>::value>{});
return arr;
}else {
auto u = std::tuple_cat(wdh<sets>(std::make_tuple(is), std::make_index_sequence<sizeof...(is)>{})...);
auto r = recursive_flatten_tuple<sets>(u);
auto d = to_array<sets>(r, std::make_index_sequence<std::tuple_size<decltype(r)>::value>{});
return d;
}
}
template<std::size_t range, std::size_t sets>
constexpr auto cartesian_product(){
static_assert( (range > 0), "lowest input must be cartesian<1,1>" );
static_assert( (sets > 0), "lowest input must be cartesian<1,1>" );
return ct_i<sets-1>(std::make_index_sequence<range>{});
}
int main()
{
constexpr auto crt = cartesian_product<3, 2>();
for(auto&& ele : crt){
std::cout << std::get<0>(ele) << " " << std::get<1>(ele) << std::endl;
}
return 0;
}
Since I was also working on a solution I thought I post it aswell (although very similar to Artyer's answer). Same premise, we replace the tuple with an array and just iterate over the elements, incrementing them one by one.
Note that the power function is broken, so if you need power values <0 or non-integer types you have to fix it.
template <typename It, typename T>
constexpr void setAll(It begin, It end, T value)
{
for (; begin != end; ++begin)
*begin = value;
}
template <typename T, std::size_t I>
constexpr void increment(std::array<T, I>& counter, T max)
{
for (auto idx = I; idx > 0;)
{
--idx;
if (++counter[idx] >= max)
{
setAll(counter.begin() + idx, counter.end(), 0); // because std::fill is not constexpr yet
}
else
{
break;
}
}
}
// std::pow is not constexpr
constexpr auto power = [](auto base, auto power)
{
auto result = base;
while (--power)
result *= base;
return result;
};
template<std::size_t range, std::size_t sets>
constexpr auto cartesian_product()
{
std::array<std::array<int, sets>, power(range, sets)> products{};
std::array<int, sets> currentSet{};
for (auto& product : products)
{
product = currentSet;
increment(currentSet, static_cast<int>(range));
}
return products;
}
int main()
{
constexpr auto crt = cartesian_product<5, 3>();
for (auto&& ele : crt)
{
for (auto val : ele)
std::cout << val << " ";
std::cout << "\n";
}
return 0;
}
Example
With Boost.Mp11, this is... alright, it's not a one-liner, but it's still not so bad:
template <typename... Lists>
using list_product = mp_product<mp_list, Lists...>;
template <typename... Ts>
constexpr auto list_to_tuple(mp_list<Ts...>) {
return std::make_tuple(int(Ts::value)...);
}
template <typename... Ls>
constexpr auto list_to_array(mp_list<Ls...>) {
return std::array{list_to_tuple(Ls{})...};
}
template <size_t R, size_t N>
constexpr auto cartesian_product()
{
using L = mp_repeat_c<mp_list<mp_iota_c<R>>, N>;
return list_to_array(mp_apply<list_product, L>{});
}
With C++20, you can declare the two helper function templates as lambdas inside of cartesian_product, which makes this read nicer (top to bottom instead of bottom to top).
Explanation of what's going on, based on the OP example of cartesian_product<3, 2>:
mp_iota_c<R> gives us the list [0, 1, 2] (but as integral constant types)
mp_repeat_c<mp_list<mp_iota_c<R>>, N> gives us [[0, 1, 2], [0, 1, 2]]. We just repeat the list, but we want a list of lists (hence the extra mp_list in the middle).
mp_apply<list_product, L> does mp_product, which is a cartesian product of all the lists you pass in... sticking the result in an mp_list. This gives you [[0, 0], [0, 1], [0, 2], ..., [2, 2]], but as an mp_list of mp_list of integral constants.
At this point the hard part is over, we just have to convert the result back to an array of tuples. list_to_tuple takes an mp_list of integral constants and turns that into a tuple<int...> with the right values. And list_to_array takes an mp_list of mp_lists of integral constants and turns that into an std::array of tuples.
A slightly different approach using just the single helper function:
template <template <typename...> class L,
typename... Ts, typename F>
constexpr auto unpack(L<Ts...>, F f) {
return f(Ts{}...);
}
template <size_t R, size_t N>
constexpr auto cartesian_product()
{
using P = mp_apply_q<
mp_bind_front<mp_product_q, mp_quote<mp_list>>,
mp_repeat_c<mp_list<mp_iota_c<R>>, N>>;
return unpack(P{},
[](auto... lists){
return std::array{
unpack(lists, [](auto... values){
return std::make_tuple(int(values)...);
})...
};
});
}
This approach is harder to read though, but it's the same algorithm.
You can do this without recursion easily. Notice that each tuple is the digits of numbers from 0 to range ** sets in base range, so you could increment a counter (Or apply to a std::index_sequence) and calculate each value one after the other.
Here's an implementation (That returns a std::array of std::arrays, which works mostly the same as std::tuples as you can get<N>, tuple_size and tuple_element<N> on a std::array, though if you really wanted you can convert them to std::tuples):
#include <cstddef>
#include <array>
namespace detail {
constexpr std::size_t ipow(std::size_t base, std::size_t exponent) noexcept {
std::size_t p = 1;
while (exponent) {
if (exponent % 2 != 0) {
p *= base;
}
exponent /= 2;
base *= base;
}
return p;
}
}
template<std::size_t range, std::size_t sets>
constexpr std::array<std::array<std::size_t, sets>, detail::ipow(range, sets)>
cartesian_product() noexcept {
constexpr std::size_t size = detail::ipow(range, sets);
std::array<std::array<std::size_t, sets>, size> result{};
for (std::size_t i = 0; i < size; ++i) {
std::size_t place = size;
for (std::size_t j = 0; j < sets; ++j) {
place /= range;
result[i][j] = (i / place) % range;
}
}
return result;
}
Here's a test link: https://www.onlinegdb.com/By_X9wbrI
Note that (empty_set)^0 is defined as a set containing an empty set here, but that can be changed by making ipow(0, 0) == 0 instead of 1
I was trying it out just for fun and I ended with pretty much the same idea as #Timo, just with a different format/style.
#include <iostream>
#include <array>
using namespace std;
template<size_t range, size_t sets>
constexpr auto cartesian_product() {
// how many elements = range^sets
constexpr auto size = []() {
size_t x = range;
size_t n = sets;
while(--n != 0) x *= range;
return x;
}();
auto products = array<array<size_t, sets>, size>();
auto counter = array<size_t, sets>{}; // array of zeroes
for (auto &product : products) {
product = counter;
// counter increment and wrapping/carry over
counter.back()++;
for (size_t i = counter.size()-1; i != 0; i--) {
if (counter[i] == range) {
counter[i] = 0;
counter[i-1]++;
}
else break;
}
}
return products;
}
int main() {
auto prods = cartesian_product<3, 6>();
}
I basically have a counter array which I increment manually, like so:
// given cartesian_product<3, 4>
[0, 0, 0, 0]
[0, 0, 0, 1]
[0, 0, 0, 2]
[0, 0, 1, 0] // carry over
...
...
[2, 2, 2, 2]
Pretty much just how you would do it by hand.
Example
If you want it in compile-time, you should only employ compile-time evaluations over compile-time data structures. As #Barry pointed above, using Boost.Mp11 greatly facilitates it. Of course you can do reimplement the relevant fundamental functions in plain C++17 on your own:
#include <iostream>
template<class T> struct Box {
using type = T;
};
template<class... Types> struct List {};
template<class Car, class Cdr> struct Cons;
template<class Car, class Cdr> using ConsT = typename Cons<Car, Cdr>::type;
template<class Car, class... Cdr> struct Cons<Car, List<Cdr...>>: Box<List<Car, Cdr...>> {};
using Nil = List<>;
template<std::size_t i, class L> struct Nth;
template<std::size_t i, class L> using NthT = typename Nth<i, L>::type;
template<std::size_t i, class... Ts> struct Nth<i, List<Ts...>>: std::tuple_element<i, std::tuple<Ts...>> {};
template<class L> struct Head;
template<class L> using HeadT = typename Head<L>::type;
template<class Car, class... Cdr> struct Head<List<Car, Cdr...>>: Box<Car> {};
template<class L> struct Tail;
template<class L> using TailT = typename Tail<L>::type;
template<class Car, class... Cdr> struct Tail<List<Car, Cdr...>>: Box<List<Cdr...>> {};
template<class... Lists> struct Concat;
template<class... Lists> using ConcatT = typename Concat<Lists...>::type;
template<class T, class... Rest> struct Concat<T, Rest...>: Cons<T, ConcatT<Rest...>> {};
template<class Head, class... Tail, class... Rest> struct Concat<List<Head, Tail...>, Rest...>: Cons<Head, ConcatT<List<Tail...>, Rest...>> {};
template<class... Rest> struct Concat<Nil, Rest...>: Concat<Rest...> {};
template<> struct Concat<>: Box<Nil> {};
template<class T, class Subspace> struct Prepend;
template<class T, class Subspace> using PrependT = typename Prepend<T, Subspace>::type;
template<class T, class... Points> struct Prepend<T, List<Points...>>: Box<List<ConsT<T, Points>...>> {};
template<class T> struct Prepend<T, Nil>: Box<List<List<T>>> {};
template<class Range, class Subspace> struct Product;
template<class Range, class Subspace> using ProductT = typename Product<Range, Subspace>::type;
template<class Range, class Subspace> struct Product: Concat<PrependT<HeadT<Range>, Subspace>, ProductT<TailT<Range>, Subspace>> {};
template<class Subspace> struct Product<Nil, Subspace>: Box<Nil> {};
template<std::size_t i> using IntValue = std::integral_constant<std::size_t, i>;
template<class Seq> struct IntegerSequence;
template<class Seq> using IntegerSequenceT = typename IntegerSequence<Seq>::type;
template<std::size_t... is> struct IntegerSequence<std::index_sequence<is...>>: Box<List<IntValue<is>...>> {};
template<std::size_t n> using Range = IntegerSequenceT<std::make_index_sequence<n>>;
template<std::size_t dimensions, std::size_t range> struct CartesianCube;
template<std::size_t dimensions, std::size_t range> using CartesianCubeT = typename CartesianCube<dimensions, range>::type;
template<std::size_t dimensions, std::size_t range> struct CartesianCube: Product<Range<range>, CartesianCubeT<dimensions - 1, range>> {};
template<std::size_t range> struct CartesianCube<0, range>: Box<Nil> {};
template<std::size_t i> std::ostream &operator<<(std::ostream &s, IntValue<i>) {
return s << '<' << i << '>';
}
template<class... Ts> std::ostream &operator<<(std::ostream &s, List<Ts...>);
namespace detail_ {
template<class L, std::size_t... is> std::ostream &printList(std::ostream &s, L, std::index_sequence<is...>) {
return ((s << (is == 0? "" : ", ") << NthT<is, L>{}), ...), s;
}
}
template<class... Ts> std::ostream &operator<<(std::ostream &s, List<Ts...>) {
return detail_::printList(s << "List{", List<Ts...>{}, std::index_sequence_for<Ts...>{}) << '}';
}
int main() {
std::cout << CartesianCubeT<2, 3>{} << '\n';
}
Note that CartesianCubeT here is actually a List of Lists of integral_constants. Once you have those, converting them into run-time values is trivial. Note that cartesian_product does not even have to be a function, since the whole data set is evaluated at compile-time it can be a templated value.
I want to write a template function that writes tables to HDF5 files.
The signature should look similar to
template<typename record> void writeTable(const std::vector<record>& data);
where record is a struct, or
template<typename... elements>
void writeTable(const std::vector<std::tuple<elements...>>& data);
The actual implementation would have more parameters to determine the destionation, etc.
To write the data I need to define a HDF5 compound type, which contains the name and the offset of the members. Usually you would use the HOFFSET macro the get the field offset, but as I don't know the struct fields beforehand I can't do that.
What I tried so far was constructing a struct type from the typename pack. The naive implementation did not have standard layout, but the implementation here does. All that's left is get the offsets of the members. I would like to expand the parameter pack into an initializer list with the offsets:
#include <vector>
template<typename... members> struct record {};
template<typename member, typename... members> struct record<member, members...> :
record<members...> {
record(member m, members... ms) : record<members...>(ms...), tail(m) {}
member tail;
};
template<typename... Args> void
make_table(const std::string& name, const std::vector<record<Args...>>& data) {
using record_type = record<Args...>;
std::vector<size_t> offsets = { get_offset(record_type,Args)... };
}
int main() {
std::vector<record<int, float>> table = { {1, 1.0}, {2, 2.0} };
make_table("table", table);
}
Is there a possible implementation for get_offset? I would think not, because in the case of record<int, int> it would be ambiguous. Is there another way to do it?
Or is there any other way I could approach this problem?
Calculating offsets is quite simple. Given a tuple with types T0, T1 ... TN. The offset of T0 is 0 (as long as you use alignas(T0) on your char array. The offset of T1 is the sizeof(T0) rounded up to alignof(T1).
In general, the offset of TB (which comes after TA) is round_up(offset_of<TA>() + sizeof(TA), alignof(TB)).
Calculating the offsets of elements in a std::tuple could be done like this:
constexpr size_t roundup(size_t num, size_t multiple) {
const size_t mod = num % multiple;
return mod == 0 ? num : num + multiple - mod;
}
template <size_t I, typename Tuple>
struct offset_of {
static constexpr size_t value = roundup(
offset_of<I - 1, Tuple>::value + sizeof(std::tuple_element_t<I - 1, Tuple>),
alignof(std::tuple_element_t<I, Tuple>)
);
};
template <typename Tuple>
struct offset_of<0, Tuple> {
static constexpr size_t value = 0;
};
template <size_t I, typename Tuple>
constexpr size_t offset_of_v = offset_of<I, Tuple>::value;
Here's a test suite. As you can see from the first test, the alignment of elements is taken into account.
static_assert(offset_of_v<1, std::tuple<char, long double>> == 16);
static_assert(offset_of_v<2, std::tuple<char, char, long double>> == 16);
static_assert(offset_of_v<3, std::tuple<char, char, char, long double>> == 16);
static_assert(offset_of_v<4, std::tuple<char, char, char, char, long double>> == 16);
static_assert(offset_of_v<0, std::tuple<int, double, int, char, short, long double>> == 0);
static_assert(offset_of_v<1, std::tuple<int, double, int, char, short, long double>> == 8);
static_assert(offset_of_v<2, std::tuple<int, double, int, char, short, long double>> == 16);
static_assert(offset_of_v<3, std::tuple<int, double, int, char, short, long double>> == 20);
static_assert(offset_of_v<4, std::tuple<int, double, int, char, short, long double>> == 22);
static_assert(offset_of_v<5, std::tuple<int, double, int, char, short, long double>> == 32);
I hardcoded the offsets in the above tests. The offsets are correct if the following tests succeed.
static_assert(sizeof(char) == 1 && alignof(char) == 1);
static_assert(sizeof(short) == 2 && alignof(short) == 2);
static_assert(sizeof(int) == 4 && alignof(int) == 4);
static_assert(sizeof(double) == 8 && alignof(double) == 8);
static_assert(sizeof(long double) == 16 && alignof(long double) == 16);
std::tuple seems to store it's elements sequentially (without sorting them to optimize padding). That's proven by the following tests. I don't think the standard requires std::tuple to be implemented this way so I don't think the following tests are guaranteed to succeed.
template <size_t I, typename Tuple>
size_t real_offset(const Tuple &tup) {
const char *base = reinterpret_cast<const char *>(&tup);
return reinterpret_cast<const char *>(&std::get<I>(tup)) - base;
}
int main(int argc, char **argv) {
using Tuple = std::tuple<int, double, int, char, short, long double>;
Tuple tup;
assert((offset_of_v<0, Tuple> == real_offset<0>(tup)));
assert((offset_of_v<1, Tuple> == real_offset<1>(tup)));
assert((offset_of_v<2, Tuple> == real_offset<2>(tup)));
assert((offset_of_v<3, Tuple> == real_offset<3>(tup)));
assert((offset_of_v<4, Tuple> == real_offset<4>(tup)));
assert((offset_of_v<5, Tuple> == real_offset<5>(tup)));
}
Now that I've gone to all of this effort, would that real_offset function suit your needs?
This is a minimal implementation of a tuple that accesses a char[] with offset_of. This is undefined behavior though because of the reinterpret_cast. Even though I'm constructing the object in the same bytes and accessing the object in the same bytes, it's still UB. See this answer for all the standardese. It will work on every compiler you can find but it's UB so just use it anyway. This tuple is standard layout (unlike std::tuple). If the elements of your tuple are all trivially copyable, you can remove the copy and move constructors and replace them with memcpy.
template <typename... Elems>
class tuple;
template <size_t I, typename Tuple>
struct tuple_element;
template <size_t I, typename... Elems>
struct tuple_element<I, tuple<Elems...>> {
using type = std::tuple_element_t<I, std::tuple<Elems...>>;
};
template <size_t I, typename Tuple>
using tuple_element_t = typename tuple_element<I, Tuple>::type;
template <typename Tuple>
struct tuple_size;
template <typename... Elems>
struct tuple_size<tuple<Elems...>> {
static constexpr size_t value = sizeof...(Elems);
};
template <typename Tuple>
constexpr size_t tuple_size_v = tuple_size<Tuple>::value;
constexpr size_t roundup(size_t num, size_t multiple) {
const size_t mod = num % multiple;
return mod == 0 ? num : num + multiple - mod;
}
template <size_t I, typename Tuple>
struct offset_of {
static constexpr size_t value = roundup(
offset_of<I - 1, Tuple>::value + sizeof(tuple_element_t<I - 1, Tuple>),
alignof(tuple_element_t<I, Tuple>)
);
};
template <typename Tuple>
struct offset_of<0, Tuple> {
static constexpr size_t value = 0;
};
template <size_t I, typename Tuple>
constexpr size_t offset_of_v = offset_of<I, Tuple>::value;
template <size_t I, typename Tuple>
auto &get(Tuple &tuple) noexcept {
return *reinterpret_cast<tuple_element_t<I, Tuple> *>(tuple.template addr<I>());
}
template <size_t I, typename Tuple>
const auto &get(const Tuple &tuple) noexcept {
return *reinterpret_cast<tuple_element_t<I, Tuple> *>(tuple.template addr<I>());
}
template <typename... Elems>
class tuple {
alignas(tuple_element_t<0, tuple>) char storage[offset_of_v<sizeof...(Elems), tuple<Elems..., char>>];
using idx_seq = std::make_index_sequence<sizeof...(Elems)>;
template <size_t I>
void *addr() {
return static_cast<void *>(&storage + offset_of_v<I, tuple>);
}
template <size_t I, typename Tuple>
friend auto &get(const Tuple &) noexcept;
template <size_t I, typename Tuple>
friend const auto &get(Tuple &) noexcept;
template <size_t... I>
void default_construct(std::index_sequence<I...>) {
(new (addr<I>()) Elems{}, ...);
}
template <size_t... I>
void destroy(std::index_sequence<I...>) {
(get<I>(*this).~Elems(), ...);
}
template <size_t... I>
void move_construct(tuple &&other, std::index_sequence<I...>) {
(new (addr<I>()) Elems{std::move(get<I>(other))}, ...);
}
template <size_t... I>
void copy_construct(const tuple &other, std::index_sequence<I...>) {
(new (addr<I>()) Elems{get<I>(other)}, ...);
}
template <size_t... I>
void move_assign(tuple &&other, std::index_sequence<I...>) {
(static_cast<void>(get<I>(*this) = std::move(get<I>(other))), ...);
}
template <size_t... I>
void copy_assign(const tuple &other, std::index_sequence<I...>) {
(static_cast<void>(get<I>(*this) = get<I>(other)), ...);
}
public:
tuple() noexcept((std::is_nothrow_default_constructible_v<Elems> && ...)) {
default_construct(idx_seq{});
}
~tuple() {
destroy(idx_seq{});
}
tuple(tuple &&other) noexcept((std::is_nothrow_move_constructible_v<Elems> && ...)) {
move_construct(other, idx_seq{});
}
tuple(const tuple &other) noexcept((std::is_nothrow_copy_constructible_v<Elems> && ...)) {
copy_construct(other, idx_seq{});
}
tuple &operator=(tuple &&other) noexcept((std::is_nothrow_move_assignable_v<Elems> && ...)) {
move_assign(other, idx_seq{});
return *this;
}
tuple &operator=(const tuple &other) noexcept((std::is_nothrow_copy_assignable_v<Elems> && ...)) {
copy_assign(other, idx_seq{});
return *this;
}
};
Alternatively, you could use this function:
template <size_t I, typename Tuple>
size_t member_offset() {
return reinterpret_cast<size_t>(&std::get<I>(*static_cast<Tuple *>(nullptr)));
}
template <typename Member, typename Class>
size_t member_offset(Member (Class::*ptr)) {
return reinterpret_cast<size_t>(&(static_cast<Class *>(nullptr)->*ptr));
}
template <auto MemPtr>
size_t member_offset() {
return member_offset(MemPtr);
}
Once again, this is undefined behavior (because of the nullptr dereference and the reinterpret_cast) but it will work as expected with every major compiler. The function cannot be constexpr (even though member offset is a compile-time calculation).
Not sure to understand what do you exactly want but... what about using recursion based on a index sequence (starting from C++14) something as follows?
#include <vector>
#include <utility>
#include <iostream>
template <typename... members>
struct record
{ };
template <typename member, typename... members>
struct record<member, members...> : record<members...>
{
record (member m, members... ms) : record<members...>(ms...), tail(m)
{ }
member tail;
};
template <std::size_t, typename, std::size_t = 0u>
struct get_offset;
template <std::size_t N, typename A0, typename ... As, std::size_t Off>
struct get_offset<N, record<A0, As...>, Off>
: public get_offset<N-1u, record<As...>, Off+sizeof(A0)>
{ };
template <typename A0, typename ... As, std::size_t Off>
struct get_offset<0u, record<A0, As...>, Off>
: public std::integral_constant<std::size_t, Off>
{ };
template <typename... Args, std::size_t ... Is>
auto make_table_helper (std::string const & name,
std::vector<record<Args...>> const & data,
std::index_sequence<Is...> const &)
{ return std::vector<std::size_t>{ get_offset<Is, record<Args...>>::value... }; }
template <typename... Args>
auto make_table (std::string const & name,
std::vector<record<Args...>> const & data)
{ return make_table_helper(name, data, std::index_sequence_for<Args...>{}); }
int main ()
{
std::vector<record<int, float>> table = { {1, 1.0}, {2, 2.0} };
auto v = make_table("table", table);
for ( auto const & o : v )
std::cout << o << ' ';
std::cout << std::endl;
}
Unfortunately isn't an efficient solution because the last value is calculated n-times.
I want variadic template parameters must unique.
I know when multi inheritance, identical classes inheritance is not allowed.
struct A{};
struct B: A, A{}; // error
Using this rule, I made a little code.
#include <type_traits>
template< class T> struct id{};
template< class ...T> struct base_all : id<T> ... {};
template< class ... T>
struct is_unique
{
template< class ... U>
static constexpr bool test( base_all<U...> * ) noexcept { return true; }
template< class ... U>
static constexpr bool test( ... ) noexcept { return false;}
static constexpr bool value = test<T...>(0);
};
int main()
{
constexpr bool b = is_unique<int, float, double>::value; // false -- Why?
constexpr bool c = is_unique< int, char, int>::value; // false
static_assert( b == true && c == false , "!");// failed.
}
But my program is not worked as I expected. What's wrong?
//UPDATE:
//Thanks, I fix my error:
//
// #include <type_traits>
// #include <cstddef>
//
// template< class ... U> struct pack{};
//
// template< class T> struct id{};
// template< class T> struct base_all;
// template< class ... T> struct base_all< pack<T...> > : id<T> ... {};
//
//
//
// template< class ... T>
// struct is_unique
// {
// template< class P, std::size_t = sizeof(base_all<P>) >
// struct check;
//
// template< class ...U>
// static constexpr bool test(check< pack<U...> > * ) noexcept { return true;}
//
// template< class ... U>
// static constexpr bool test(...)noexcept { return false;}
//
// static constexpr bool value = test<T...>(0);
// };
//
// int main()
// {
// constexpr bool b = is_unique<int, float, double>::value; // true
// constexpr bool c = is_unique< int, char, int>::value; // false
//
// static_assert( b == true && c == false , "!");// success.
// }
//
Q: somebody can explain, why it's failed?
UPDATE2: My previous update was illegal :)). Legal form, but it compiled O(N) time.
#include <cstddef>
#include <iostream>
#include <type_traits>
namespace mpl
{
template< class T > using invoke = typename T :: type ;
template< class C, class I, class E > using if_t = invoke< std::conditional< C{}, I, E> >;
template< class T > struct id{};
struct empty{};
template< class A, class B > struct base : A, B {};
template< class B , class ... > struct is_unique_impl;
template< class B > struct is_unique_impl<B>: std::true_type{};
template< class B, class T, class ... U>
struct is_unique_impl<B, T, U...> : if_t< std::is_base_of< id<T>, B>, std::false_type, is_unique_impl< base<B,id<T>>, U...> >{};
template< class ...T >struct is_unique : is_unique_impl< empty, T ... > {};
} // mpl
int main()
{
constexpr bool b = mpl::is_unique<int, float, double>::value;
constexpr bool c = mpl::is_unique< int, char, int > :: value;
static_assert( b == true , "!");
static_assert( c == false, "!");
return 0;
}
Passing a pointer to base_all<U...> merely requires the existence of a declaration of base_all<U...>. Without attempting the to access the definition, the compiler won't detect that the type is actually ill-defined. One approach to mitigate that problem would be to use an argument which requires a definition of base_all<U...>, e.g.:
template< class ...T> struct base_all
: id<T> ...
{
typedef int type;
};
// ...
template< class ... U>
static constexpr bool test(typename base_all<U...>::type) noexcept
{
return true;
}
Although the above answers the question, it fail to compile: the multiple inheritance created isn't in a suitable context to be considered for SFINAE. I don't think you can leverage the rule on not allowing the same base inherited from twice. The relevant test can be implemented differently, though:
#include <type_traits>
template <typename...>
struct is_one_of;
template <typename F>
struct is_one_of<F>
{
static constexpr bool value = false;
};
template <typename F, typename S, typename... T>
struct is_one_of<F, S, T...>
{
static constexpr bool value = std::is_same<F, S>::value
|| is_one_of<F, T...>::value;
};
template <typename...>
struct is_unique;
template <>
struct is_unique<> {
static constexpr bool value = true;
};
template<typename F, typename... T>
struct is_unique<F, T...>
{
static constexpr bool value = is_unique<T...>::value
&& !is_one_of<F, T...>::value;
};
int main()
{
constexpr bool b = is_unique<int, float, double>::value;
constexpr bool c = is_unique< int, char, int>::value;
static_assert( b == true && c == false , "!");
}
Using C++17, you can use fold expressions. Especially with larger numbers of template parameters, this version can be orders of magnitude faster (and less memory hungry) than the other solutions presented here:
#include <type_traits>
template <typename T>
struct Base{};
template <typename... Ts>
struct TypeSet : Base<Ts>...
{
template<typename T>
constexpr auto operator+(Base<T>)
{
if constexpr (std::is_base_of_v<Base<T>, TypeSet>)
return TypeSet{};
else
return TypeSet<Ts..., T>{};
}
constexpr auto size() const -> std::size_t
{
return sizeof...(Ts);
}
};
template<typename... Ts>
constexpr auto are_unique() -> bool
{
constexpr auto set = (TypeSet<>{} + ... + Base<Ts>{});
return set.size() == sizeof...(Ts);
}
int main()
{
static_assert(are_unique<int, float, char, char*>());
static_assert(not are_unique<int, float, char, char>());
}
See https://godbolt.org/z/_ELpyJ
If you have a C++17 compiler and your type lists are not too large, it can be done in just 4 lines of code using fold expressions. The algorithm itself has quadratic complexity, though.
template<typename T, typename... Types>
constexpr bool are_types_unique_v = (!std::is_same_v<T, Types> && ...) && are_types_unique_v<Types...>;
template<typename T>
constexpr bool are_types_unique_v<T> = true;
Another O(logN) instantiation depth solution. It still needs a major cleanup, comments, namespaces, renaming, and reduced code duplication.
Kudos again to Xeo, whose O(logN) instantiation depth version of gen_seq this (again) relies upon.
#include <cstddef>
// using aliases for cleaner syntax
template<class T> using Invoke = typename T::type;
template<std::size_t...> struct seq{ using type = seq; };
template<class S1, class S2> struct concat;
template<std::size_t... I1, std::size_t... I2>
struct concat<seq<I1...>, seq<I2...>>
: seq<I1..., (sizeof...(I1)+I2)...>{};
template<class S1, class S2>
using Concat = Invoke<concat<S1, S2>>;
template<std::size_t N> struct gen_seq;
template<std::size_t N> using GenSeq = Invoke<gen_seq<N>>;
template<std::size_t N>
struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};
template<> struct gen_seq<0> : seq<>{};
template<> struct gen_seq<1> : seq<0>{};
Aside from the indices sequence generation, this solution should even have O(1) instantiation depth. Instead of multiple inheritance, it uses a std::array<std::false_type, size> to do a O(1)-instantiation-depth OR via SFINAE.
Implementation of is_one_of. Note that "is one of" is the opposite concept of "are unique".
#include <array>
// check if `T` is in `Us...`
template<class T, class... Us>
struct is_one_of
{
template<class T1>
static constexpr auto SFINAE(int)
-> decltype( std::array<std::false_type, sizeof...(Us)>
{{std::is_same<T1, Us>{}...}} )
{ return {}; /* only to suppress warning */ }
template<class...>
static constexpr int SFINAE(...) { return 42; }
template<class T1>
static constexpr bool test()
{
return std::is_same<decltype(SFINAE<T1>(0)), int>{};
}
static constexpr bool value = test<T>();
constexpr operator bool() const { return value; }
};
Implementation of are_unique:
namespace detail
{
// `Any` type with a generic no-constraint ctor
// to discard a number of arguments for a function template
template<std::size_t>
struct Any
{
template<class T>
constexpr Any(T&&) {}
};
// `wrapper` is used as a substitute for `declval`,
// and can keep track if `T` is a reference
template<class T>
struct wrapper { using type = T; };
template<std::size_t I, class T, class... Us>
struct is_one_of_pack
{
template<std::size_t... I1s>
struct helper
{
template<class... Remaining>
static constexpr bool deduce_remaining(Any<I1s>..., Remaining...)
{
// unique <-> is one of
return not is_one_of<T, typename Remaining::type...>{};
}
};
template<std::size_t... I1s>
static constexpr bool deduce_seq(seq<I1s...>)
{
return helper<I1s...>::template deduce_remaining(wrapper<Us>()...);
}
static constexpr bool create_seq()
{
return deduce_seq(gen_seq<I+1>{});
}
using type = std::integral_constant<bool, create_seq()>;
};
template<class... Packs>
constexpr auto SFINAE(int)
-> decltype( std::array<std::true_type, sizeof...(Packs)>
{{typename Packs::type{}...}} )
{ return {}; /* only to suppress warning */ }
template<class...>
static constexpr int SFINAE(...) { return 42; }
template<class... Packs>
constexpr bool test()
{
return std::is_same<decltype(SFINAE<Packs...>(0)), int>{};
}
template<class... Ts, std::size_t... Is>
constexpr bool deduce_seq(seq<Is...>)
{
return test< is_one_of_pack<Is, Ts, Ts...>... >();
}
}
template<class... Ts>
struct are_unique
: std::integral_constant<bool,
detail::deduce_seq<Ts...>(gen_seq<sizeof...(Ts)>{})>
{};
Usage example:
#include <iostream>
#include <iomanip>
int main()
{
bool a = are_unique<bool, char, int>();
bool b = are_unique<bool, char, int, bool>();
bool c = are_unique<bool, char, bool, int>();
std::cout << std::boolalpha;
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;
}
template<typename ... _Types>
class unique_types;
template<typename _T1, typename _T2, typename ... _Tail>
class unique_types<_T1,_T2,_Tail...> :
virtual public unique_types<_T1, _T2>
, virtual public unique_types<_T1, _Tail ...>
, virtual public unique_types<_T2, _Tail ...>
{
protected:
using check_current = unique_types<_T1, _T2>;
using check_first = unique_types<_T1, _Tail ...>;
using check_second = unique_types<_T2, _Tail ...>;
public:
constexpr static const bool value = check_current::value && check_first::value && check_second::value;
};
template<typename _T1, typename _T2>
class unique_types<_T1, _T2, _T2>
{
public:
constexpr static const bool value = false;
};
template<typename _T1, typename ... _Tail>
class unique_types<_T1, _T1, _Tail ...>
{
public:
constexpr static const bool value = false;
};
template<typename _T1, typename _T2>
class unique_types<_T1, _T2, _T1>
{
public:
constexpr static const bool value = false;
};
template<typename _T1, typename _T2>
class unique_types<_T1,_T2>
{
public:
constexpr static const bool value = true;
};
template<typename _T1>
class unique_types<_T1,_T1>
{
public:
constexpr static const bool value = false;
};
template<typename _T1>
class unique_types<_T1>
{
public:
constexpr static const bool value = true;
};
class A
{
public:
A() = default;
};
inline void test()
{
const bool unique = unique_types<int, short, float, A>::value;
assert(unique == true);
const bool unique2 = unique_types<int, A, short, float, A>::value;
assert(unique2 == false);
const bool unique3 = unique_types<A, int, short, float, A>::value;
assert(unique3 == false);
const bool unique4 = unique_types<int, short, A, float, A>::value;
assert(unique4 == false);
const bool unique5 = unique_types<int, short, float, A, A>::value;
assert(unique5 == false);
const bool unique6 = unique_types<int>::value;
assert(unique6 == true);
const bool unique7 = unique_types<int, int>::value;
assert(unique7 == false);
const bool unique8 = unique_types<int, int, char>::value;
assert(unique8 == false);
const bool unique9 = unique_types<int, char, int>::value;
assert(unique9 == false);
const bool unique10 = unique_types<char, int, int>::value;
assert(unique10 == false);
const bool unique11 = unique_types<int, int, A, char>::value;
assert(unique11 == false);
const bool unique12 = unique_types<int, A, char, int>::value;
assert(unique12 == false);
const bool unique13 = unique_types<A, char, int, int>::value;
assert(unique13 == false);
}
I would like iterate over an tuple in some way with member function templates (for later create a new type of tuple from the given template type T).
However, the break condition (function) is not used so I get this error:
invalid use of incomplete type: 'class std::tuple_element<0ul, std::tuple<> >'
The problem seems to be, that even though N == size of the tuple, std::tuple_element_t is evaluated for N != size and not handled as SFINAE.
Both examples showing different not working solutions. What do I wrong?
Note: The function for evaluated with is_same is omitted to minimize the example.
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0, typename std::enable_if_t<N == size>* = nullptr>
int get()
{
return 0;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
Live Example 1
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0>
std::enable_if_t<N == size, int> get()
{
return 0;
}
template<typename T, std::size_t N = 0>
std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int> get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
Live Example 2
One workaround would be to use a third function to evaluate until sizeof tuple - 2 and than evaluate sizeof tuple - 1, but Is this really necessary?
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N == size - 1) && std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return 1;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N == size - 1) && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return 2;
}
template<typename T, std::size_t N = 0, typename std::enable_if_t<(N < size - 1) && !std::is_same<T, std::tuple_element_t<N, tuple>>::value>* = nullptr>
int get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
Live Example 3
As suggested by #PiotrSkotnicki in the comments to the question, here is your second example once fixed:
#include <type_traits>
#include <tuple>
template<typename...Ts>
struct A
{
using tuple = std::tuple<Ts...>;
static constexpr std::size_t size = sizeof...(Ts);
template<typename T, std::size_t N = 0>
std::enable_if_t<N == size-1, int>
get()
{
return std::is_same<T, std::tuple_element_t<N, tuple>>::value ? N : 0;
}
template<typename T, std::size_t N = 0>
std::enable_if_t<N != size-1 && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int>
get()
{
return get<T, N + 1>() - 1;
}
};
int main()
{
A<int, float, double, float, float> a;
return a.get<char>();
}
What was the problem?
Consider the following line:
std::enable_if_t<N != size && !std::is_same<T, std::tuple_element_t<N, tuple>>::value, int> get()
In this case, N was substituted in order to evaluate the condition of the enable_if, even when N == size (substitution is mandatory to find that N == size indeed).
Thus, the tuple_element_t (let me say) issued an out of range and that's why you got the compilation error.
I've simply updated your code to avoid reaching size while iterating over N. It was a matter of using size-1 as a value on which to switch between functions.
In a comment to this answer the OP said:
It does solve the problem but not for automatic type return type deduction based on which function is used (returning int was just an example). I should have been clearer on this.
It follows a minimal, working example that probably solves the problem also for that.
It's far easier to reason in terms of inheritance and tag dispatching in this case, so as to reduce the boilerplate due to sfinae. Moreover, one can use specializations to introduce specific behaviors for specific types if needed.
The final case, the one for the type that is not part of the types list, is easily handled in a dedicated function as well.
It follows the code:
#include <type_traits>
#include <tuple>
template<typename>
struct tag {};
template<typename...>
struct B;
template<typename T, typename... Ts>
struct B<T, Ts...>: B<Ts...> {
using B<Ts...>::get;
auto get(tag<T>) {
return T{};
}
};
template<>
struct B<> {
template<typename T>
auto get(tag<T>) {
return nullptr;
}
};
template<typename...Ts>
struct A: private B<Ts...>
{
template<typename T>
auto get() {
return B<Ts...>::get(tag<T>{});
}
};
int main()
{
A<int, float, double, float, float> a;
static_assert(std::is_same<decltype(a.get<char>()), std::nullptr_t>::value, "!");
static_assert(std::is_same<decltype(a.get<float>()), float>::value, "!");
}
What about using an additional struct that, with partial specialization, can avoid the use of std::tuple_element_t ?
I mean, something like
template <typename T, std::size_t N>
struct checkType
{ constexpr static bool value
= std::is_same<T, std::tuple_element_t<N, tuple>>::value; };
template <typename T>
struct checkType<T, size>
{ constexpr static bool value = false; };
template <typename, std::size_t N = 0>
std::enable_if_t<N == size, int> get ()
{ return 0; }
template <typename T, std::size_t N = 0>
std::enable_if_t<(N < size) && ! checkType<T, N>::value, int> get()
{ return get<T, N + 1>() - 1; }