Related
Concepts can be used to put a constraint on types as template parameters like the example below:
template<typename t, int v>
concept the_concept1 = sizeof(t) > v;
template<int v, the_concept1<v> t>
struct some_struct1{};
I am trying to use a similar method with values like the example below:
template<int v1, int v2>
concept the_concept2 = v1 > v2;
template<int v1, the_concept2<v1> v2>
struct some_struct2{};
But with G++ 10 I am getting the following error message:
error: ‘the_concept2’ does not constrain a type
So I was wondering if concepts can be used to put a constraint on values? If so then how should I do it?
Edit: My final goal is to use the concept in declaration of a template structure with variadic template parameters like:
template<typename t, std::size_t ... v>
struct the_struct;
And I need a concept to check if every v is less than sizeof(t).
If you want to use a concept as a named type constraint on a template parameter, as in your example, the concept needs to apply to a type template parameter.
You can still define concepts that apply only to e.g. non-type template parameters, however, as long as you use it in a context which allows these; e.g. using a requires-clause:
template<int v1, int v2>
concept the_concept2 = v1 > v2;
template<int v1, int v2> requires the_concept2<v1, v2>
struct some_struct2{};
using valid = some_struct2<42, 41>;
//using invalid = some_struct2<42, 42>; // constraints not satisfied
Another example applied on a function template or a member function of a class template:
template<int v1, int v2>
concept the_concept2 = v1 > v2;
template <int a, int b>
void bar() requires the_concept2<a, b> {}
template <int a, int b>
struct Foo {
static void bar() requires the_concept2<a, b> {}
};
int main() {
bar<2, 1>();
Foo<2, 1>::bar();
//bar<2, 2>(); // candidate template ignored: constraints not satisfied
//Foo<2, 2>::bar(); // invalid reference to function 'bar': constraints not satisfied
}
The following OP edit (which basically asks an entirely different question)
Edit: My final goal is to use the concept in declaration of a template
structure with variadic template parameters like:
template<typename t, std::size_t ... v>
struct the_struct;
And I need a concept to check if every v is less than sizeof(t).
can be achieved by specifying the concept itself to apply for variadic non-type template parameters that are expanded in the sizeof(T) > v check using parameter pack expansion:
#include <cstddef>
#include <cstdint>
template<typename T, std::size_t... v>
concept the_concept1 = (... && (sizeof(T) > v));
template<typename T, std::size_t... vs> requires the_concept1<T, vs...>
struct the_struct;
using TypeOfSize4Bytes = uint32_t;
using valid = the_struct<TypeOfSize4Bytes, 1, 3, 2, 1>;
using also_valid = the_struct<TypeOfSize4Bytes>;
//using invalid = the_struct<TypeOfSize4Bytes, 1, 2, 4>; // error: constraints not satisfied
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;
}
Concepts can be used to put a constraint on types as template parameters like the example below:
template<typename t, int v>
concept the_concept1 = sizeof(t) > v;
template<int v, the_concept1<v> t>
struct some_struct1{};
I am trying to use a similar method with values like the example below:
template<int v1, int v2>
concept the_concept2 = v1 > v2;
template<int v1, the_concept2<v1> v2>
struct some_struct2{};
But with G++ 10 I am getting the following error message:
error: ‘the_concept2’ does not constrain a type
So I was wondering if concepts can be used to put a constraint on values? If so then how should I do it?
Edit: My final goal is to use the concept in declaration of a template structure with variadic template parameters like:
template<typename t, std::size_t ... v>
struct the_struct;
And I need a concept to check if every v is less than sizeof(t).
If you want to use a concept as a named type constraint on a template parameter, as in your example, the concept needs to apply to a type template parameter.
You can still define concepts that apply only to e.g. non-type template parameters, however, as long as you use it in a context which allows these; e.g. using a requires-clause:
template<int v1, int v2>
concept the_concept2 = v1 > v2;
template<int v1, int v2> requires the_concept2<v1, v2>
struct some_struct2{};
using valid = some_struct2<42, 41>;
//using invalid = some_struct2<42, 42>; // constraints not satisfied
Another example applied on a function template or a member function of a class template:
template<int v1, int v2>
concept the_concept2 = v1 > v2;
template <int a, int b>
void bar() requires the_concept2<a, b> {}
template <int a, int b>
struct Foo {
static void bar() requires the_concept2<a, b> {}
};
int main() {
bar<2, 1>();
Foo<2, 1>::bar();
//bar<2, 2>(); // candidate template ignored: constraints not satisfied
//Foo<2, 2>::bar(); // invalid reference to function 'bar': constraints not satisfied
}
The following OP edit (which basically asks an entirely different question)
Edit: My final goal is to use the concept in declaration of a template
structure with variadic template parameters like:
template<typename t, std::size_t ... v>
struct the_struct;
And I need a concept to check if every v is less than sizeof(t).
can be achieved by specifying the concept itself to apply for variadic non-type template parameters that are expanded in the sizeof(T) > v check using parameter pack expansion:
#include <cstddef>
#include <cstdint>
template<typename T, std::size_t... v>
concept the_concept1 = (... && (sizeof(T) > v));
template<typename T, std::size_t... vs> requires the_concept1<T, vs...>
struct the_struct;
using TypeOfSize4Bytes = uint32_t;
using valid = the_struct<TypeOfSize4Bytes, 1, 3, 2, 1>;
using also_valid = the_struct<TypeOfSize4Bytes>;
//using invalid = the_struct<TypeOfSize4Bytes, 1, 2, 4>; // error: constraints not satisfied
I'm working on an Arduino project, which means the C++ dialect is currently the gnu++11 superset of C++11, and stdlib is not available (no tuples, no arrays, no nothing; namespace std is just empty!).
For optimization reasons (the CPU has 16K of FLASH, 2K of RAM and this particular low-voltage version runs at 8MHz) I want the compiler to pre-compute as much as possible to provide runtime code, especially the interrupt service routines, with "friendly" data.
Now what I would like to do is the following:
given a list of (unique) integers, I want to extract the values that match an arbitrary filter.
Then I want to build an index table that will allow to reach the filtered elements through their initial indices
For instance 2,10,4,7,9,3 with the filter value < 8 could yield the filtered list 2,4,7,3 and the index table 0,-1,1,2,-1,3.
The order of the elements in the filtered array does not matter as long as the index table remains consistent.
I insist on the fact that I want constant arrays. Producing these data dynamically would be trivial, but I want the compiler to do the job, without executing a single instruction at runtime.
The initial list would be given by a plain #define, and the results would be in constant arrays, e.g:
#define my_list 2,10,4,7,9,3
constexpr bool filter (int value) { return value < 8; }
const int filtered_list [] = filter_list <filter>(my_list);
const size_t filtered_index[] = filter_index<filter>(my_list);
The question is, how to implement these filter_list and filter_index templates with barebone C++11 and no stdlib, if at all feasible?
I'm not interested in error handling, the abnormal cases like empty lists or duplicate values are already taken care of. I'd rather like to see the simplest possible implementation, even if some assumptions are made about data validity.
The exact form of the templates, filter or the initial list do not matter either. All that matters is to get the arrays from an unique list definition.
For instance I would not mind a syntax where each element of the list is declared separately (though I can't really imagine how that could work).
I would prefer to have a self-contained C++ source. On the other hand, if what Python could achieve in a couple dozen lines requires pages of cryptic templates, including the rewriting of std::array and std::tuple, I'd rather write some external preprocessor.
Here is a small implementation of compile-time filtering, reproducing small parts of the standard library in a minimalist way. It includes an example of usage at the end. (It probably isn't possible to implement the filtering as a function, since C++ doesn't allow the result type of a function to depend on the values of the arguments. So, you would have to have the result type have enough storage for the case where the predicate always returns true which seems like it would be a show-stopper for your use case. That is why the approach here is to do the filtering using template metaprogramming first, and then convert the results to an array wrapper object.)
#include <sys/types.h>
template <typename T, size_t N>
struct array {
T elem[N];
constexpr size_t size() const { return N; }
constexpr T operator[](size_t i) const { return elem[i]; }
T* begin() { return elem; }
const T* begin() const { return elem; }
T* end() { return elem + N; }
const T* end() const { return elem; }
};
template <typename T>
struct array<T, 0> {
constexpr size_t size() const { return 0; }
T* begin() { return nullptr; }
const T* begin() const { return nullptr; }
T* end() { return nullptr; }
const T* end() const { return nullptr; }
};
template <typename T, T... x>
struct static_sequence { };
template <bool p, typename TrueT, typename FalseT>
struct conditional;
template <typename TrueT, typename FalseT>
struct conditional<true, TrueT, FalseT> {
using type = TrueT;
};
template <typename TrueT, typename FalseT>
struct conditional<false, TrueT, FalseT> {
using type = FalseT;
};
template <bool p, typename TrueT, typename FalseT>
using conditional_t = typename conditional<p, TrueT, FalseT>::type;
template <typename T, T x, typename S>
struct static_sequence_cons;
template <typename T, T x, T... Ss>
struct static_sequence_cons<T, x, static_sequence<T, Ss...>> {
using type = static_sequence<T, x, Ss...>;
};
template <typename T, T x, typename S>
using static_sequence_cons_t = typename static_sequence_cons<T, x, S>::type;
template <typename T, bool(*pred)(T), T... N>
struct filter;
template <typename T, bool(*pred)(T)>
struct filter<T, pred> {
using type = static_sequence<T>;
};
template <typename T, bool(*pred)(T), T hd, T... tl>
struct filter<T, pred, hd, tl...> {
private:
using filter_tl = typename filter<T, pred, tl...>::type;
public:
using type = conditional_t<pred(hd),
static_sequence_cons_t<T, hd, filter_tl>,
filter_tl>;
};
template <typename T, bool(*pred)(T), T... N>
using filter_t = typename filter<T, pred, N...>::type;
template <ssize_t curr_index, typename T, bool(*pred)(T), T... N>
struct filter_index;
template <ssize_t curr_index, typename T, bool(*pred)(T)>
struct filter_index<curr_index, T, pred> {
using type = static_sequence<ssize_t>;
};
template <ssize_t curr_index, typename T, bool(*pred)(T), T hd, T... tl>
struct filter_index<curr_index, T, pred, hd, tl...> {
using type = conditional_t<pred(hd),
static_sequence_cons_t<ssize_t, curr_index, typename filter_index<curr_index + 1, T, pred, tl...>::type>,
static_sequence_cons_t<ssize_t, -1, typename filter_index<curr_index, T, pred, tl...>::type>>;
};
template <typename T, bool(*pred)(T), T... N>
using filter_index_t = typename filter_index<0, T, pred, N...>::type;
template <typename T, T... x>
constexpr array<T, sizeof...(x)> static_sequence_to_array(
static_sequence<T, x...>) {
return array<T, sizeof...(x)> { x... };
}
//
// EXAMPLE USAGE
//
constexpr bool even(int n) {
return n % 2 == 0;
}
constexpr auto x = static_sequence_to_array(
filter_t<int, even, 0, 1, 2, 3, 4>{});
constexpr auto i = static_sequence_to_array(
filter_index_t<int, even, 0, 1, 2, 3, 4>{});
static_assert(x.size() == 3, "Bad filter");
static_assert(x[0] == 0, "Bad filter");
static_assert(x[1] == 2, "Bad filter");
static_assert(x[2] == 4, "Bad filter");
static_assert(i.size() == 5, "Bad filter_index");
static_assert(i[0] == 0, "Bad filter_index");
static_assert(i[1] == -1, "Bad filter_index");
static_assert(i[2] == 1, "Bad filter_index");
static_assert(i[3] == -1, "Bad filter_index");
static_assert(i[4] == 2, "Bad filter_index");
There is a way to avoid most of the boilerplate using function templates instead of full classes. The last class template is needed because there is no return type deduction for functions in c++11. int is used instead of typename T to skip unimportant template parameters. The code could be slimmed further when atmel updates their toolchain to gcc5 or newer with c++14 support.
#define LIST 2,10,4,7,9,3
constexpr bool less8(int v) { return v < 8; }
typedef bool(*predicate)(int);
template<int... values>
struct seq {};
template<int N>
struct array {
const int data[N];
};
template<int... values>
constexpr array<sizeof...(values)> to_array(seq<values...>) { return {{ values... }}; }
template<typename trueType, typename falseType>
constexpr falseType select(seq<0>, trueType, falseType) { return {}; }
template<typename trueType, typename falseType>
constexpr trueType select(seq<1>, trueType, falseType) { return {}; }
template<int... first, int... second>
constexpr seq<first..., second...> append(seq<first...>, seq<second...>) { return {}; }
template<predicate p, typename N, typename V>
struct filter_impl;
template<predicate p, int next>
struct filter_impl<p, seq<next>, seq<>> {
using type = seq<>;
};
template<predicate p, int next, int first, int... rest>
struct filter_impl<p, seq<next>, seq<first, rest...>> {
using type = decltype(
append(
select(seq<p(first)>{}, seq<next>{}, seq<-1>{}),
typename filter_impl<p, decltype(select(seq<p(first)>{}, seq<next+1>{}, seq<next>{})), seq<rest...>>::type{}
)
);
};
extern constexpr auto const_indices = to_array(filter_impl<less8, seq<0>, seq<LIST>>::type{});
My own answer just to put things together from a mundane point of view.
It is heavily based on Daniel Schepler's solution. I certainly would still be stuck on this problem without his help. Rewriting my own version was more like a learning exercise.
//////////////////////////////////////////////////////////////////////////////
// should allow to compile outside Arduino environment without std includes
typedef unsigned char uint8_t;
typedef unsigned size_t;
//////////////////////////////////////////////////////////////////////////////
// pseudo-stl
// barebone std::array
template <typename T, size_t N> struct array {
T elem[N];
constexpr size_t size() { return N; }
constexpr T operator[](size_t i) { return elem[i]; }
};
// barebone std::integer_sequence
template <typename T, T... Values> struct integer_sequence
{
typedef T value_type;
};
//////////////////////////////////////////////////////////////////////////////
// sequence filtering
// predicate functions prototype (means the sequence type must be convertible to int)
typedef bool(*predicate)(int);
// LISP-like 'if' (selects a parameter according to a boolean value)
template <bool Check, typename IfTrue, typename IfFalse> struct s_if;
template <typename IfTrue, typename IfFalse> struct s_if<true , IfTrue, IfFalse> {using type = IfTrue ;};
template <typename IfTrue, typename IfFalse> struct s_if<false, IfTrue, IfFalse> {using type = IfFalse;};
template <bool Check, typename IfTrue, typename IfFalse>
using f_if = typename s_if<Check, IfTrue, IfFalse>::type;
// LISP-like 'cons' for integer_sequence
template <typename T, T Car, typename Cdr> struct s_integer_sequence_cons;
template <typename T, T Car, T... Cdr> struct s_integer_sequence_cons<T, Car, integer_sequence<T, Cdr...>>
{ using type = integer_sequence<T, Car, Cdr...>; };
template <typename T, T Car, typename Cdr>
using f_cons = typename s_integer_sequence_cons<T, Car, Cdr>::type;
// LISP-like 'append' for integer_sequence
template <typename T, typename S1, typename S2> struct s_integer_sequence_append;
template <typename T, T... S1, T... S2> struct s_integer_sequence_append<T, integer_sequence<T, S1...>, integer_sequence<T, S2...>>
{ using type = integer_sequence<T, S1..., S2...>; };
template <typename S1, typename S2>
using f_append = typename s_integer_sequence_append<S1::value_type, S1, S2>::type;
// filter an integer_sequence according to a predicate
template <typename Sequence, predicate pred> struct s_filter;
template <typename T, predicate pred> struct s_filter<integer_sequence<T>, pred>
{
using type = integer_sequence<T>; // termination condition
};
template <typename T, T Car, T...Cdr, predicate pred> struct s_filter<integer_sequence<T, Car, Cdr...>, pred>
{
using tail = typename s_filter<integer_sequence<T, Cdr...>, pred>::type; // forward recursion on the sequence tail
using type = f_if<pred(Car), // if current element satisfies the predicate
f_cons<T, Car, tail>, // add it to the list
tail>; // else skip it
};
template <typename Sequence, predicate pred>
using f_filter = typename s_filter<Sequence, pred>::type;
//////////////////////////////////////////////////////////////////////////////
// now for the indexation...
// returns the index of a value in a list of values, or -1 if not found
template <int I , typename T> constexpr T find_index (T elem) { return -1; }
template <int I=0, typename T, typename... List> constexpr T find_index (T elem, T val, List... rest)
{ return elem == val ? I : find_index<I+1>(elem, rest...); }
// builds an index list allowing to reach each value final position from their initial position
template <typename Target, typename Origin> struct s_index_list;
template <typename T, typename Origin> struct s_index_list<integer_sequence<T>, Origin>
{
using type = integer_sequence<T>; // termination of the Target list
};
template <typename T, T Car, T... Cdr, T...Origin> struct s_index_list<integer_sequence<T, Car, Cdr...>, integer_sequence<T, Origin...>>
{
// as usual, the only way to loop is to recurse...
using tail = typename s_index_list<integer_sequence<T, Cdr...>, integer_sequence<T, Origin...>>::type;
using type = f_cons<T, find_index(Car, Origin...), tail>;
};
template <typename Target, typename Origin>
using f_index = typename s_index_list<Target, Origin>::type;
//////////////////////////////////////////////////////////////////////////////
// implementing sequences as arrays
// turn an integer_sequence into a (constant) array
template <typename T, T... x> constexpr array<T, sizeof...(x)> integer_sequence_to_array(integer_sequence<T, x...>)
{ return array<T, sizeof...(x)> { x... }; }
//////////////////////////////////////////////////////////////////////////////
// Putting all this marvelous piece of engineering to use
//
// our initial list
#define input_list 2,10,4,7,9,3
// convert the list into a sequence
typedef integer_sequence<uint8_t, input_list> input_sequence;
// define filtering predicates
constexpr bool test_group_1(int n) { return (n >> 3) == 0; } // values from 0 to 7
constexpr bool test_group_2(int n) { return (n >> 3) == 1; } // values from 8 to 15
// compute the two split sequences
typedef f_filter<input_sequence, test_group_1> sequence_1; // <unsigned char, 2u, 4u, 7u, 3u>
typedef f_filter<input_sequence, test_group_2> sequence_2; // <unsigned char, 10u, 9u>
// append them
typedef f_append<sequence_1, sequence_2> output_sequence; // <unsigned char, 2u, 4u, 7u, 3u, 10u, 9u>
// compute indexes
typedef f_index<output_sequence, input_sequence> output_indexes; // <unsigned char, 0u, 2u, 3u, 5u, 1u, 4u>
// turn the results into arrays
constexpr auto const_values = integer_sequence_to_array(output_sequence{}); // [2, 4, 7, 3,10, 9]
constexpr auto const_indexes = integer_sequence_to_array(output_indexes {}); // [0, 2, 3, 5, 1, 4]
A few afterthoughts
The purpose of this code is to generate a couple of integer arrays at compile time. This could easily be done with 20 lines of any language able to handle text files (Python, PHP, Perl, awk, you name it...), doing the trivial filtering and indexing in a couple of loops and replacing the line
#define my_list 2,10,4,7,9,3
with
const int filtered_list [] = {2,4,7,3};
const size_t filtered_index[] = {0,-1,1,2,-1,3};
before passing the modified source to the actual C++ compiler.
The only reason I wanted a self-contained C++ program is the Arduino environment. It is meant to be very simple, and as such is considerably restrictive. You can't really ask a casual Arduino programmer to tweak makefiles or use external code generation tools, so if you want to provide an "Arduino-friendly" module you're basically stuck with this stdlib-less gnu++11 compiler.
Now since the C++ preprocessor is clearly not up to that kind of job, the only choice left is template metaprogramming.
I can't say I really understand how it works, I just used it as a last resort. I'm an embedded software programmer, and my functional programming skills are certainly nothing to write home about.
Still I tried to put my rusty LISP notions to good use, but basic tools like cons, append or if were apparently nowhere to be found, and re-writing them from scratch felt largely like trying to reinvent the wheel. Having to coax the pattern-matching engine into recursion to implement simple loops was also rather painful and made for pretty eye-watering code. Hopefully there's a few useful tricks I missed there.
All this being said, and to my surprise, the lack of stdlib was not really a problem. You can whip up the bare minimum in a few lines of code, provided you throw caution to the wind and kiss the syntactic sugar goodbye.
I finally came up with something that actually does the job in a bit less than 100 lines of very obfuscated code.
I'm not looking for efficiency here, my lists will hardly hold more than a dozen values, but I sure would be happy to watch and learn a way to achieve the same result with less source code. Any takers?
While playing around with compile-time string (variadic lists of char) manipulation, I needed to implement a way of checking if a compile-time string contained another (smaller) compile-time string.
This was my first attempt:
template<int I1, int I2, typename, typename> struct Contains;
template<int I1, int I2, char... Cs1, char... Cs2>
struct Contains<I1, I2, CharList<Cs1...>, CharList<Cs2...>>
{
using L1 = CharList<Cs1...>;
using L2 = CharList<Cs2...>;
static constexpr int sz1{L1::size};
static constexpr int sz2{L2::size};
using Type = std::conditional
<
(I1 >= sz1),
std::false_type,
std::conditional
<
(L1::template at<I1>() != L2::template at<I2>()),
typename Contains<I1 + 1, 0, L1, L2>::Type,
std::conditional
<
(I2 == sz2 - 1),
std::true_type,
typename Contains<I1 + 1, I2 + 1, L1, L2>::Type
>
>
>;
};
I find this solution extremely easy to read and reason about. Unfortunately, it doesn't work.
The compiler always tries to instantiate every single branch of std::conditional, even those which are not taken. To put it in another way, short-circuiting isn't happening.
This causes Contains to be instantiated infinitely.
I've solved my original problem by separating every std::conditional block in a separate template class where the condition results are handled as partial specializations.
It works, but unfortunately I find it very hard to read/modify.
Is there a way to lazily instantiate a template type and be close to my original solution?
This is an example of what the code could look like:
using Type = std::conditional
<
(I1 >= sz1),
std::false_type,
std::conditional
<
(L1::template at<I1>() != L2::template at<I2>()),
DeferInstantiation<typename Contains<I1 + 1, 0, L1, L2>::Type>,
std::conditional
<
(I2 == sz2 - 1),
std::true_type,
DeferInstantiation<typename Contains<I1 + 1, I2 + 1, L1, L2>::Type>
>
>
>;
Is it somehow possible to implement DeferInstantiation<T>?
The compiler always tries to instantiate every single branch of std::conditional, even those which are not taken.
To put it in another way, short-circuiting isn't happening.
std::conditional<B,T,F> is provided for the purpose of executing a compiletime
choice between given types T and F, depending on the boolean B. The
choice is effected by specialization. When B is true, the instantiated specialization is:
std::conditional<true,T,F>
{
typedef T type;
};
And when B is false, the the instantiated specialization is:
std::conditional<false,T,F>
{
typedef F type;
};
Note that to instantiate either specialization, both T and F must
be instantiated. There are no "branches". The notion of "short-circuiting" the
instantiation of either std::conditional<true,T,F> or std::conditional<false,T,F>
could only mean not doing it.
So no, it is not possible to implement DeferInstantiation<U>, for type parameter
U, such that an instantiation of
std::conditional<{true|false},DeferInstantiation<T>,DeferInstantiation<F>>
will not entail instantiation of DeferInstantiation<T> and DeferInstantiation<F>>,
and therefore of T, and of F.
For executing a compiletime choice as to which or two or more templates shall be
instantiated, the language provides specialization (as just illustrated
by the definition of std::conditional<B,T,F> itself); it provides function template overload
resolution, and it provides SFINAE.
Specialization and overload resolution can each be synergetically leveraged with
SFINAE, via the library support of std::enable_if<B,T>
The problem that has obstructed you in crafting the particular recursive meta-function
that you want is not one of choosing between given types but of choosing the template
into which recursive instantiation shall be directed.std::conditional is not
to the purpose. #Pradhan's answer demonstrates that a template different from std::conditional
can well be written to effect a compiletime choice between two templates, without
entailing that both of them shall be instantiated. He applies specialization to do it.
As you say, you have already figured out a specialization solution to the
problem. This is in principle the right way to recursively control
template selection in recursive meta-functions. However, with the advent of
constexpr, recursive meta-functions do not command anything like the market share of
problems that they formerly did, and most of the brain-ache they occasioned
is a thing of the past.
The particular problem here - determining at compiletime whether one string is a substring
of another - can be solved without grappling with template meta-programming, and without
representing compiletime strings otherwise than as traditional string literals:
#include <cstddef>
constexpr std::size_t str_len(char const *s)
{
return *s ? 1 + str_len(s + 1) : 0;
}
constexpr bool
is_substr(char const * src, char const *targ,
std::size_t si = 0, std::size_t ti = 0)
{
return !targ[ti] ? true :
str_len(src + si) < str_len(targ + ti) ? false :
src[si] == targ[ti] ?
is_substr(src,targ,si + 1, ti + 1) :
is_substr(src,targ,si + 1, 0);
}
// Compiletime tests...
static_assert(is_substr("",""),"");
static_assert(is_substr("qwerty",""),"");
static_assert(is_substr("qwerty","qwerty"),"");
static_assert(is_substr("qwerty","qwert"),"");
static_assert(is_substr("qwerty","werty"),"");
static_assert(is_substr("qwerty","wert"),"");
static_assert(is_substr("qwerty","er"),"");
static_assert(!is_substr("qwerty","qy"),"");
static_assert(!is_substr("qwerty","et"),"");
static_assert(!is_substr("qwerty","qwertyz"),"");
static_assert(!is_substr("qwerty","pqwerty"),"");
static_assert(!is_substr("","qwerty"),"");
int main()
{
return 0;
}
This will compile as C++11 or better.
You may well have reasons for wishing to represent compiletime strings
as CharList<char ...> other than thus rendering them amenable to
TMP compiletime queries such as this. We can see that CharList<char ...Cs>
has a static constant size member evaluating to sizeof...(Cs) and has
a static at<N>() member function evaluating to the Nth of the ...Cs.
In that case (assuming that at<N>() is debugged), you might adapt
is_substr to be a template function expecting CharList<char ...>
parameters on roughly the following lines:
#include <type_traits>
template<
class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0>
constexpr typename
std::enable_if<(TargI == TargList::size && SrcI <= SrcList::size),bool>::type
is_substr()
{
return true;
}
template<
class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0>
constexpr typename
std::enable_if<(TargI < TargList::size && SrcI == SrcList::size),bool>::type
is_substr()
{
return false;
}
template<
class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0>
constexpr typename
std::enable_if<(TargI < TargList::size && SrcI < SrcList::size),bool>::type
is_substr()
{
return SrcList::template at<SrcI>() == TargList::template at<TargI>() ?
is_substr<SrcList,TargList,SrcI + 1,TargI + 1>() :
is_substr<SrcList,TargList,SrcI + 1,0>();
}
which illustrates the application of SFINAE, leveraged by std::enable_if
Finally, you could also be interested in this program:
#include <iostream>
template<char const * Arr>
struct string_lit_type
{
static constexpr const char * str = Arr;
static constexpr std::size_t size = str_len(str);
static constexpr char at(std::size_t i) {
return str[i];
}
};
constexpr char arr[] = "Hello World\n";
int main()
{
std::cout << string_lit_type<arr>::str;
std::cout << string_lit_type<arr>::size << std::endl;
std::cout << string_lit_type<arr>::at(0) << std::endl;
return 0;
}
which prints:
Hello World
12
H
(Code compiled with g++ 4.9, clang 3.5)
Here's a generic template to allow deferred instantiation by simply not instantiating :)
template <bool B, template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ArgsTuple>
struct LazyConditional;
template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args>
struct LazyConditional<true, TrueTemplate, FalseTemplate, std::tuple<Args...>>
{
using type = TrueTemplate<Args...>;
};
template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args>
struct LazyConditional<false, TrueTemplate, FalseTemplate, std::tuple<Args...>>
{
using type = FalseTemplate<Args...>;
};
For completeness, a simple example demonstrating its use:
#include <iostream>
#include <type_traits>
#include <tuple>
template <typename T>
struct OneParam
{
void foo(){std::cout << "OneParam" << std::endl;}
};
template <typename T, typename U>
struct TwoParam
{
void foo(){std::cout << "TwoParam" << std::endl;}
};
template <bool B, template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ArgsTuple>
struct LazyConditional;
template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args>
struct LazyConditional<true, TrueTemplate, FalseTemplate, std::tuple<Args...>>
{
using type = TrueTemplate<Args...>;
};
template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args>
struct LazyConditional<false, TrueTemplate, FalseTemplate, std::tuple<Args...>>
{
using type = FalseTemplate<Args...>;
};
template <typename ... Args>
struct OneOrTwoParam
{
using type = typename LazyConditional<sizeof...(Args)==1, OneParam, TwoParam, std::tuple<Args...> >::type;
};
int main()
{
OneOrTwoParam<int>::type().foo();
OneOrTwoParam<int, int>::type().foo();
return 0;
}
This prints:
OneParam
TwoParam
I agree with the OP that it is unfortunate to not have short-circuiting in std::conditional (or call it SFINAE in the unentered branch, so that incorrect types do not lead to an error).
I had the same problem in my code and could solve it by using if constexpr in a constexpr lambda. So, instead of
using type = std::conditional_t<logical, A, B>;
use
auto get_type = []()
{
if constexpr(logical)
{
return std::declval<A>();
}
else
{
return std::declval<B>();
}
};
using type = decltype(get_type());
which, however, is by far less readable.