Building static strings with C++ Metaprogramming - c++

I'm writing an open source sqlite interface library (mSqliteCpp for instance) that uses c++ classes and types to correctly manage sqlite databases.
So far the library is heavily using TMP to build SQL strings, but I found a possible issue that may somehow affect the efficiency of the library.
I'm using classes to manage Fields definitions, classes definitions, queries and statements. Basically to define a table or a SELECT statement, one defines the fields using proper FieldDef<T> objects, then pass them to a statement builder that returns the correctly formatted SQL statement.
For example:
auto fldId = sqlite::makeFieldDef("id", sqlite::FieldType::Integer());
auto fldName = sqlite::makeFieldDef("name", sqlite::FieldType::Text());
auto fldValue = sqlite::makeFieldDef("value", sqlite::FieldType::Real());
sqlite::statements::Select select("Table", fldId, fldName, fldValue);
ASSERT_EQ(select.string(), "SELECT id,name,value FROM Table;");
Note that each field is strongly typed by the FieldType type passed to the makeFieldDef function, but different fields with similar type can be exchanged. For this reason the SQL statement in the ASSERT call is built at runtime.
I would like to have them built at compile time, at least under certain conditions. For example, developer could use a different class or maybe the constexpr keyword. But at this time I've no idea if this could be achieved.
What are the possible patterns? Can strings be statically built through TMP? I'm using C++11/14.
Thank you.

what I'm looking for is a solution that will make most of the computation with strings at compile time [...] At the moment I'm just courious about how this could be implemented.
Not an answer to your sqlite question but... if your "computation with strings at compile time" are simple as concatenation... just to play with constexpr and template metaprogramming...
std::string isn't constexpr but std::array can be.
So you can define a fake string as an alias for an array of chars.
template <std::size_t N>
using fakeStr = std::array<char, N>;
You can convert a string literal to a fake string as follows (with an helper function and playing with type traits)
template <std::size_t N, std::size_t ... Is>
constexpr fakeStr<sizeof...(Is)> mFSh (char const (& str)[N],
std::index_sequence<Is...> const &)
{ return { { str[Is]... } }; }
template <std::size_t N>
constexpr auto makeFakeStr (char const (& str)[N])
{ return mFSh(str, std::make_index_sequence<N-1>{}); }
The fake string concatenation is simple as follows
template <std::size_t N1, std::size_t ... I1s,
std::size_t N2, std::size_t ... I2s>
constexpr fakeStr<N1+N2> cFSh (fakeStr<N1> const & s1,
std::index_sequence<I1s...> const &,
fakeStr<N2> const & s2,
std::index_sequence<I2s...> const &)
{ return { { s1[I1s]..., s2[I2s]... } }; }
template <std::size_t N1, std::size_t N2>
constexpr auto concatFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return cFSh(s1, std::make_index_sequence<N1>{},
s2, std::make_index_sequence<N2>{}); }
and a constexpr comparison can be done as follows (with C++17 the helper function can be simpler)
template <std::size_t N1, std::size_t N2, std::size_t ... Is>
constexpr bool cmpFSh (fakeStr<N1> const & s1,
fakeStr<N2> const & s2,
std::index_sequence<Is...> const &)
{
using unused = bool[];
bool ret { true };
(void) unused { true, ret &= s1[Is] == s2[Is] ... };
return ret;
}
template <std::size_t N1, std::size_t N2>
constexpr bool compareFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return (N1 == N2) && cmpFSh(s1, s2, std::make_index_sequence<N1>{}); }
If you want a std::string from a fakeStr, the following code (not constexpr, obviously) can help
template <std::size_t N, std::size_t ... Is>
std::string fFSh (fakeStr<N> const & s, std::index_sequence<Is...> const &)
{ return { s[Is]..., '\0' }; }
template <std::size_t N>
auto fromFakeString (fakeStr<N> const & s)
{ return fFSh(s, std::make_index_sequence<N>{}); }
The following is a full (C++14) working example
#include <array>
#include <string>
#include <iostream>
#include <type_traits>
template <std::size_t N>
using fakeStr = std::array<char, N>;
template <std::size_t N, std::size_t ... Is>
constexpr fakeStr<sizeof...(Is)> mFSh (char const (& str)[N],
std::index_sequence<Is...> const &)
{ return { { str[Is]... } }; }
template <std::size_t N>
constexpr auto makeFakeStr (char const (& str)[N])
{ return mFSh(str, std::make_index_sequence<N-1>{}); }
template <std::size_t N1, std::size_t ... I1s,
std::size_t N2, std::size_t ... I2s>
constexpr fakeStr<N1+N2> cFSh (fakeStr<N1> const & s1,
std::index_sequence<I1s...> const &,
fakeStr<N2> const & s2,
std::index_sequence<I2s...> const &)
{ return { { s1[I1s]..., s2[I2s]... } }; }
template <std::size_t N1, std::size_t N2>
constexpr auto concatFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return cFSh(s1, std::make_index_sequence<N1>{},
s2, std::make_index_sequence<N2>{}); }
template <std::size_t N1, std::size_t N2, std::size_t ... Is>
constexpr bool cmpFSh (fakeStr<N1> const & s1,
fakeStr<N2> const & s2,
std::index_sequence<Is...> const &)
{
using unused = bool[];
bool ret { true };
(void) unused { true, ret &= s1[Is] == s2[Is] ... };
return ret;
}
template <std::size_t N1, std::size_t N2>
constexpr bool compareFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return (N1 == N2) && cmpFSh(s1, s2, std::make_index_sequence<N1>{}); }
template <std::size_t N, std::size_t ... Is>
std::string fFSh (fakeStr<N> const & s, std::index_sequence<Is...> const &)
{ return { s[Is]..., '\0' }; }
template <std::size_t N>
auto fromFakeString (fakeStr<N> const & s)
{ return fFSh(s, std::make_index_sequence<N>{}); }
int main()
{
constexpr auto f1 = makeFakeStr("abcd");
constexpr auto f2 = makeFakeStr("xyz");
constexpr auto f12 = concatFakeStr(f1, f2);
constexpr auto f3 = makeFakeStr("abcdxyz");
static_assert( true == compareFakeStr(f12, f3), "!" );
static_assert( false == compareFakeStr(f12, f1), "!" );
auto s12 = fromFakeString(f12);
std::cout << s12 << std::endl;
}
I repeat: just to play.

#max66's solution is probably better, but there is a proof of concept of a different approach: to store strings as char parameter packs:
template <char ...C> struct cexpr_str
{
static constexpr char value[] {C..., '\0'};
};
Here is a complete example, demonstrating how to create such strings from string literals, concatenate, and compare them:
#include <cstddef>
#include <iostream>
#include <type_traits>
#include <utility>
template <char ...C> struct cexpr_str
{
static constexpr char value[] {C..., '\0'};
};
namespace impl
{
using std::size_t;
template <typename ...P> struct cexpr_cat
{
using type = cexpr_str<>;
};
template <typename A, typename B, typename ...P> struct cexpr_cat<A, B, P...>
{
using type = typename cexpr_cat<typename cexpr_cat<A, B>::type, P...>::type;
};
template <char ...A, char ...B> struct cexpr_cat<cexpr_str<A...>, cexpr_str<B...>>
{
using type = cexpr_str<A..., B...>;
};
template <typename T> struct cexpr_cat<T>
{
using type = T;
};
template <typename, typename> struct cexpr_str_subseq {};
template <size_t ...S, char ...C> struct cexpr_str_subseq<std::index_sequence<S...>, cexpr_str<C...>>
{
using type = cexpr_str<cexpr_str<C...>::value[S]...>;
};
template <size_t N> constexpr char cexpr_str_at(const char (&str)[N], size_t pos)
{
if (pos < 0 || pos >= N)
return '\0';
else
return str[pos];
}
}
template <typename ...P> using cexpr_cat = typename impl::cexpr_cat<P...>::type;
#define MAKE_CEXPR_STR(x) ::impl::cexpr_str_subseq<::std::make_index_sequence<sizeof x - 1>,\
::cexpr_str<CEXPR_STR_16(0,x),CEXPR_STR_16(16,x),CEXPR_STR_16(32,x),CEXPR_STR_16(48,x)>>::type
#define CEXPR_STR_16(s,x) CEXPR_STR_4(s,x),CEXPR_STR_4(s+4,x),CEXPR_STR_4(s+8,x),CEXPR_STR_4(s+12,x)
#define CEXPR_STR_4(s,x) ::impl::cexpr_str_at(x,s),::impl::cexpr_str_at(x,s+1),::impl::cexpr_str_at(x,s+2),::impl::cexpr_str_at(x,s+3)
int main()
{
// You can use this macro to create a string.
// Note that it limits the length to 64, but can be easily rewritten to allow larger values.
using A = MAKE_CEXPR_STR("Hello,");
// If you don't want to use that macro, you can do this.
using B = cexpr_str<'w','o','r','l','d'>;
// You can concat any number of comma-separated strings, not just two.
using C = cexpr_cat<A,B>;
// This prints: `Hello,`+`world`=`Hello,world`.
std::cout << "`" << A::value << "`+`" << B::value << "`=`" << C::value << "`\n";
// You can use std::is_same[_v] to compare those strings:
std::cout << std::is_same_v<B,A> << '\n'; // Prints 0.
std::cout << std::is_same_v<B,MAKE_CEXPR_STR("world")> << '\n'; // Prints 1.
}

Related

Generating simple format string at compile time

I'm trying to generate a simple format string for fmt at compile time, but I can't quite figure out the string concatenation. I'm limited to c++ 14.
What I'd like is to be able to generate a format string for N items, so it could be used as follows:
auto my_string = fmt::format(format_string_struct<2>::format_string, "Item", "key1", "value1", "key2", "value2");
The generated format string would be: "{} {}={} {}={}", resulting in a formatted string of "Item key1=value1 key2=value2". The base of the format string would be "{}", and it appends a " {}={}" for each N.
I'm trying to use recursive templates, but I just can't quite get everything to work. I got some code from Concatenate compile-time strings in a template at compile time? for concatenating strings (though I get a undefined reference to `concat_impl<&base_format_string_struct::format_string, std::integer_sequence<int, 0, 1>, &format_string_parameter_struct::parameter_string, std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7> >::value' error)
template<int...I> using is = std::integer_sequence<int,I...>;
template<int N> using make_is = std::make_integer_sequence<int,N>;
constexpr auto size(const char*s) { int i = 0; while(*s!=0){++i;++s;} return i; }
template<const char*, typename, const char*, typename>
struct concat_impl;
template<const char* S1, int... I1, const char* S2, int... I2>
struct concat_impl<S1, is<I1...>, S2, is<I2...>> {
static constexpr const char value[]
{
S1[I1]..., S2[I2]..., 0
};
};
template<const char* S1, const char* S2>
constexpr auto concat {
concat_impl<S1, make_is<size(S1)>, S2, make_is<size(S2)>>::value
};
struct base_format_string_struct {
static constexpr const char format_string[] = "{}";
};
struct format_string_parameter_struct {
static constexpr const char parameter_string[] = " {}=\"{}\"";
};
template <int arg_count, const char[]... params>
struct format_string_struct:
public format_string_struct<arg_count - 1, format_string_parameter_struct, params...> {
};
template <int arg_count>
struct format_string_struct:
public format_string_struct<arg_count - 1, format_string_parameter_struct> {
};
template <const char[]... params>
struct format_string_struct<0> {
static const char* format_string() {
return concat<base_format_string_struct, params...>;
}
};
You have several typos (and a concat limited to 2 argument).
template<int...I> using is = std::integer_sequence<int,I...>;
template<int N> using make_is = std::make_integer_sequence<int,N>;
constexpr auto size(const char*s) { int i = 0; while(*s!=0){++i;++s;} return i; }
template<const char*, typename, const char*, typename>
struct concat_impl;
template<const char* S1, int... I1, const char* S2, int... I2>
struct concat_impl<S1, is<I1...>, S2, is<I2...>> {
static constexpr const char value[]
{
S1[I1]..., S2[I2]..., 0
};
};
template<const char* S1, const char* S2, const char*... Ss>
struct concat
{
constexpr static const char* value = concat_impl<S1, make_is<size(S1)>, concat<S2, Ss...>::value, make_is<size(concat<S2, Ss...>::value)>>::value;
};
template<const char* S1, const char* S2>
struct concat<S1, S2>
{
constexpr static const char* value = concat_impl<S1, make_is<size(S1)>, S2, make_is<size(S2)>>::value;
};
struct base_format_string_struct {
static constexpr const char format_string[] = "{}";
};
struct format_string_parameter_struct {
static constexpr const char parameter_string[] = " {}=\"{}\"";
};
template <int arg_count, const char*... params>
struct format_string_struct:
public format_string_struct<arg_count - 1, format_string_parameter_struct::parameter_string, params...> {
};
template <int arg_count>
struct format_string_struct<arg_count>:
public format_string_struct<arg_count - 1, format_string_parameter_struct::parameter_string> {
};
template <const char*... params>
struct format_string_struct<0, params...> {
static const char* format_string() {
return concat<base_format_string_struct::format_string, params...>::value;
}
};
Demo
But I would probably go with constexpr functions:
#if 1 // missing some constexpr for std::array/std::copy
template <typename T, std::size_t N>
struct my_array
{
T data[N];
};
template <typename CIT, typename IT>
constexpr void copy(CIT begin, CIT end, IT dest)
{
for (CIT it = begin; it != end; ++it)
{
*dest = *it;
++dest;
}
}
#endif
template <std::size_t N>
constexpr my_array<char, 2 + 8 * N + 1> format_string_struct_impl()
{
my_array<char, 2 + 8 * N + 1> res{};
constexpr char init[] = "{}"; // size == 2
constexpr char extra[] = R"( {}="{}")"; // size == 8
copy(init, init + 2, res.data);
for (std::size_t i = 0; i != N; ++i) {
copy(extra, extra + 8, res.data + 2 + i * 8);
}
return res;
}
template <std::size_t N>
constexpr my_array<char, 2 + 8 * N + 1> format_string_struct()
{
// Ensure the computation is done compile time.
constexpr auto res = format_string_struct_impl<N>();
return res;
}
Demo

Conditionally remove function calls in fold expression

I know that I can use SFINAE to disable generation of templated functions based on a condition, but that doesn't really work in this case. I want to initialize an array at compile-time that should contain values that matches a condition. Something like this:
template <std::size_t i, class ... Types, class ... Group>
constexpr auto fetch_match(const std::tuple<Group...>& candidates)
{
if constexpr (is_match<std::tuple<Group...>, i, Types...>())
{
auto& group = std::get<i>(candidates);
return group.template get<Types...>();
}
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr std::array views {
(fetch_match<indices, Types...>(candidates), ...),
};
return views;
}
I know the code above is wrong and doesn't compile. If the condition isn't filled, then I want the fold expression to not generate that function call. How would I do that?
This question might be an XY-problem, so here's a the problem in more detail.
I have a Registry that contains Groups of heterogeneous data. I want to be able to query all groups that contains the specified sub list of types. For example, for (const auto& view : registry.get<char, short, int>()) should yield an array with views of the groups that contain char, short and int. I've created a mcve below. The problem with the current code is that I have to first create the array and then copy the views, which I'd like to avoid.
#include <tuple>
#include <array>
#include <utility>
#include <type_traits>
#include <iostream>
template <typename T, typename... Ts>
constexpr bool contains = (std::is_same<T, Ts>{} || ...);
template <typename Subset, typename Set>
constexpr bool is_subset_of = false;
template <typename... Ts, typename... Us>
constexpr bool is_subset_of<std::tuple<Ts...>, std::tuple<Us...>> = (contains<Ts, Us...> && ...);
template <typename ... T>
struct View
{
const char* name_of_group; // For debugging.
std::tuple<T...> data;
};
template <typename ... Ts>
struct Group
{
using type_set = std::tuple<Ts...>;
static const char* name; // For debugging.
std::tuple<Ts...> data;
explicit Group(Ts... values) : data(values...) {}
template <typename ... Us>
[[nodiscard]] View<Us...> get() const noexcept
{
return { this->name, std::make_tuple(std::get<Us>(this->data)...) };
}
};
template <class Groups, std::size_t i, class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i, Groups>;
bool match = is_subset_of<std::tuple<Types...>, typename group_type::type_set>;
return match;
}
template <std::size_t i, class ... Types, class ... Group, class Array>
constexpr void add_matches(const std::tuple<Group...>& candidates, Array& matches, std::size_t& index)
{
if constexpr (is_match<std::tuple<Group...>, i, Types...>())
{
auto& group = std::get<i>(candidates);
matches[index++] = group.template get<Types...>();
}
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr std::size_t size = (is_match<std::tuple<Group...>, indices, Types...>() + ... + 0);
std::array<View<Types...>, size> views {};
std::size_t index = 0;
(add_matches<indices, Types...>(candidates, views, index), ...);
return views;
}
template <typename ... Group>
class Registry
{
public:
explicit Registry(Group... groups) : groups(groups...) {}
template <typename ... T>
auto get()
{
constexpr auto indices = std::index_sequence_for<Group...>{};
return get_matches<T...>(this->groups, indices);
}
private:
std::tuple<Group...> groups;
};
using A = Group<char>;
using B = Group<char, short>;
using C = Group<char, short, int>;
using D = Group<char, short, int, long long>;
// Giving the classes names for debugging purposes.
template<> const char* A::name = "A";
template<> const char* B::name = "B";
template<> const char* C::name = "C";
template<> const char* D::name = "D";
int main()
{
auto registry = Registry(A{0}, B{1,1}, C{2,2,2}, D{3,3,3,3});
// Should yield an array of size 2 with View<char, short, int>,
// one from group C and one from Group D.
for (const auto& view : registry.get<char, short, int>())
{
std::cout << "View of group: " << view.name_of_group << std::endl;
std::cout << "char: " << int(std::get<char>(view.data)) << std::endl;
std::cout << "short: " << std::get<short>(view.data) << std::endl;
std::cout << "int: " << std::get<int>(view.data) << std::endl;
}
}
Trying the suggestion in the comments, the following code is as far as I got.
template <class Groups, std::size_t i, class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i, Groups>;
bool match = is_subset_of<std::tuple<Types...>, typename group_type::type_set>;
return match;
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto build_view_array(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
std::array views {
std::get<indices>(candidates).template get<Types...>()...
};
return views;
}
template <std::size_t i, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups, i, TypeSet>())
return std::index_sequence<x..., i>{};
else
return std::index_sequence<x...>{};
}
template <std::size_t i, std::size_t j, std::size_t ... rest, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups, i, TypeSet>())
return get_matching_indices<j, rest..., Groups, TypeSet, i, x...>();
else
return get_matching_indices<j, rest..., Groups, TypeSet, x...>();
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr auto matching_indices = get_matching_indices<indices..., std::tuple<Group...>, std::tuple<Types...>>();
constexpr auto views = build_view_array<Types...>(candidates, matching_indices);
return views;
}
It feels like it should work, but it won't compile due to the following error:
/Users/tedkleinbergman/Programming/ECS/temp.cpp:76:39: error: no matching function for call to 'get_matching_indices'
constexpr auto matching_indices = get_matching_indices<indices..., std::tuple<Group...>, std::tuple<Types...>>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/tedkleinbergman/Programming/ECS/temp.cpp:92:16: note: in instantiation of function template specialization 'get_matches<char, short, int, Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> , 0, 1, 2, 3>' requested here
return get_matches<T...>(this->groups, indices);
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:118:38: note: in instantiation of function template specialization 'Registry<Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> >::get<char, short, int>' requested here
for (const auto& view : registry.get<char, short, int>())
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:57:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'Groups'
constexpr auto get_matching_indices()
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:65:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'rest'
constexpr auto get_matching_indices()
^
1 error generated.
First, start with an index_sequence filter:
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index = {};
template<std::size_t...Is, std::size_t...Js>
constexpr std::index_sequence<Is...,Js...> concatenate( std::index_sequence<Is...>, std::index_sequence<Js...> ) {
return {};
}
template <class Test>
constexpr auto filter_sequence(std::index_sequence<> sequence, Test test) {
return sequence;
}
template<std::size_t I0, std::size_t...Is, class Test>
constexpr auto filter_sequence( std::index_sequence<I0, Is...>, Test test )
{
constexpr auto tail = filter_sequence( std::index_sequence<Is...>{}, test );
if constexpr ( test(index<I0>) ) {
return concatenate( std::index_sequence<I0>{}, tail );
} else {
return tail;
}
}
we then use these primitives.
template <class Group, class ... Types>
constexpr auto get_match_indexes()
{
constexpr auto test = [](auto I){ return is_match<Group, I, Types...>(); };
constexpr auto indexes = std::make_index_sequence< std::tuple_size_v<Group> >{};
constexpr auto retval = filter_sequence( indexes, test );
return retval;
}
template<class ... Types, class Group, std::size_t...Is>
std::array<sizeof...Is, View<Types...>> get_matches(const Group& candidates, std::index_sequence<Is...> ) {
return {{
std::get<Is>(candidates).template get<Types...>(), ...
}};
}
template<class ... Types, class Group>
std::array<sizeof...Is, View<Types...>> get_matches(const Group& candidates ) {
return get_matches<Types...>( candidates, get_match_indexes<Group, Types...>() );
}
or something like that.
Note that some compilers may need to replace is_match<Group, I, Types...>() with is_match<Group, decltype(I)::value, Types...>().
There may be typos. This uses c++17 at the least.
filter_sequence uses O(n^2) template symbol length and O(n) recursive template instantiation depth. It can be improved to O(n lg n) length and O(lg n) depth with a tricky code; basically, you need to split Is... into As... and Bs... down the middle and recurse that way.
Here is a log-depth split of an index sequence:
template<class A, class B>
struct two_things {
A a;
B b;
};
template<class A, class B>
two_things(A,B)->two_things<A,B>;
template<class Seq>
constexpr auto split_sequence( index_t<0>, Seq seq ) {
return two_things{ std::index_sequence<>{}, seq };
}
template<std::size_t I0, std::size_t...Is>
constexpr auto split_sequence( index_t<1>, std::index_sequence<I0, Is...> seq ) {
return two_things{ std::index_sequence<I0>{}, std::index_sequence<Is...>{} };
}
template<std::size_t N, class Seq>
constexpr auto split_sequence( index_t<N>, Seq seq ) {
constexpr auto step1 = split_sequence( constexpr_index<N/2>, seq );
constexpr auto step2 = split_sequence( constexpr_index<N-N/2>, step1.b );
return two_things{ concatenate(step1.a, step2.a), step2.b };
}
template<std::size_t...Is>
constexpr auto halve_sequence( std::index_sequence<Is...> seq ) {
return split( index< (sizeof...(Is)) / 2u >, seq );
}
(two_things exists as a many-many-many times lighter tuple or pair than the std one).
That in turn lets you improve filter sequence.
template<std::size_t I, class Test>
constexpr auto filter_sequence( std::index_sequence<I> seq, Test test )
{
if constexpr ( test(constexpr_index<I>) ) {
return seq;
} else {
return std::index_sequence<>{};
}
}
template<std::size_t...Is, class Test>
constexpr auto filter_sequence( std::index_sequence<Is...> seq, Test test )
{
constexpr auto split = halve_sequence( seq );
constexpr auto head = filter_sequence( split.a, test );
constexpr auto tail = filter_sequence( split.b, test );
return concatenate(head, tail);
}
this version should compile faster and use less memory, especially for large numbers of elements. But you should start with the simpler one above, because (as I noted) there are probably plenty of tpyos.
Live example.

C++ "forgetting" that variable is constexpr when used as function argument

I have the following code where I am irritated by the fact that compiler is unable to see that variable passed as argument to a function is constexpr so I must use arity 0 function instead of 1 argument function.
I know this is not a compiler bug, but I wonder if there are idioms that enable to workaround this problem.
#include <array>
#include <iostream>
static constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
template <typename C, typename P, typename Y>
static constexpr void copy_if(const C& rng, P p, Y yi3ld) {
for (const auto& elem: rng) {
if (p(elem)){
yi3ld(elem);
}
}
}
// template<std::size_t N>
static constexpr auto get_evens(/* const std::array<int, N>& arr */) {
constexpr auto is_even = [](const int i) constexpr {return i % 2 == 0;};
constexpr int cnt = [/* &arr, */&is_even]() constexpr {
int cnt = 0;
auto increment = [&cnt] (const auto&){cnt++;};
copy_if(arr, is_even, increment);
return cnt;
}();
std::array<int, cnt> result{};
int idx = 0;
copy_if(arr, is_even, [&result, &idx](const auto& val){ result[idx++] = val;});
return result;
}
int main() {
// constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
for (const int i:get_evens(/* arr */)) {
std::cout << i << " " << std::endl;
}
}
If it is not obvious what I want: I would like to change get_evens signature so that it is template templated on array size N and that it takes 1 argument of type const std::array<int, N>&.
The error message when I change arr to be an function argument isn't helpful:
prog.cc:25:21: note: initializer of 'cnt' is not a constant expression
prog.cc:19:19: note: declared here
constexpr int cnt = [&arr, &is_even]()constexpr {
A function argument is never a constant expression, even if a function is used in constexpr context:
constexpr int foo(int i)
{
// i is not a constexpr
return i + 1;
}
constexpr auto i = 1;
constexpr auto j = foo(i);
To mimic a constexpr argument, use a template parameter:
template<int i>
constexpr int foo()
{
// i is constexpr
return i + 1;
}
constexpr auto i = 1;
constexpr auto j = foo<i>();
A possible solution is to use std::integer_sequence to encode integers into a type:
#include <array>
#include <iostream>
#include <type_traits>
template<typename P, typename Y, int... elements>
constexpr void copy_if_impl(P p, Y yi3ld, std::integer_sequence<int, elements...>) {
((p(elements) && (yi3ld(elements), true)), ...);
}
template<typename arr_t, typename P, typename Y>
constexpr void copy_if(P p, Y yi3ld) {
copy_if_impl(p, yi3ld, arr_t{});
}
template<typename arr_t>
constexpr auto get_evens(){
constexpr auto is_even = [](const int i) constexpr { return i % 2 == 0; };
constexpr int cnt = [&is_even]() constexpr {
int cnt = 0;
auto increment = [&cnt](const auto&) { cnt++; };
copy_if<arr_t>(is_even, increment);
return cnt;
}();
std::array<int, cnt> result{};
int idx = 0;
copy_if<arr_t>(is_even, [&result, &idx](const auto& val) {
result[idx++] = val; });
return result;
}
int main()
{
using arr = std::integer_sequence<int, 11, 22, 33, 44, 55>;
for (const int i : get_evens<arr>()) {
std::cout << i << " " << std::endl;
}
}
Addition suggested by Constantinos Glynos.
From Effective Modern C++ book by Scott Meyers, item 15, p.98:
constexpr functions can be used in contexts that demand compile-time constants. If the values of the arguments you pass to a constexpr function in such a context are known during compilation, the result will be computed during compilation. If any of the arguments’ values is not known during compilation, your code will be rejected.
When a constexpr function is called with one or more values that are not known during compilation, it acts like a normal function, computing its result at runtime. This means you don’t need two functions to perform the same operation, one for compile-time constants and one for all other values. The constexpr function does it all.
The other answer has a correct work around but I think the reasoning has nothing to do with parameters but instead to do with the lambda capture here:
constexpr int cnt = [/* &arr, */&is_even]()
Indeed we can test the various scenarios with this code:
#include <array>
#include <iostream>
template <size_t N>
constexpr int foo(const std::array<int, N>& arr) {
return [&arr] () { return arr.size(); }();
}
template <size_t N>
constexpr int bar(const std::array<int, N>& arr) {
int res{};
for (auto i : arr) {
res++;
}
return res;
}
template <size_t N>
constexpr int baz(const std::array<int, N>& arr) {
constexpr int test = [&arr] () constexpr {
return bar(arr);
}();
return test;
}
int main() {
constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
constexpr std::array<int, foo(arr)> test{};
constexpr std::array<int, bar(arr)> test2{};
constexpr std::array<int, baz(arr)> test3{};
}
Note that the line where test3 is initialized fails to compile. This, however, compiles just fine:
template <size_t N>
constexpr int baz(const std::array<int, N>& arr) {
return bar(arr);
}
So, what's the problem here? Well lambdas are really just glorified functors, and internally it'll look something like this:
struct constexpr_functor {
const std::array<int, 5>& arr;
constexpr constexpr_functor(const std::array<int, 5>& test)
: arr(test) { }
constexpr int operator()() const {
return bar(arr);
}
};
// ...
constexpr constexpr_functor t{arr};
constexpr std::array<int, t()> test3{};
Notice now that we get an error message showing the real problem:
test.cpp:36:33: note: reference to 'arr' is not a constant expression
test.cpp:33:34: note: declared here
constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
The other answer quotes Scotts Meyer's book but misinterprets the quotes. The book actually shows several examples of parameters being used in constexpr situations, but the quotes are simply saying that if you pass a non-constexpr parameter, the function can run at compile-time.
Following the Evg's suggestion, so passing the numbers as template parameters of a std::integer_sequence, but passing the integer sequence as argument of the get_evens() function, and not as template parameter, you can use the numbers directly inside get_evens().
I mean... you can simplify the get_evens() as follows (EDIT: further simplified following a suggestion from Evg (Thanks!))
template <typename T, T ... Ts>
constexpr auto get_evens (std::integer_sequence<T, Ts...> const &)
{
std::array<T, (std::size_t(!(Ts & T{1})) + ...)> result{};
std::size_t idx = 0;
((void)(Ts & 1 || (result[idx++] = Ts, true)), ...);
return result;
}
and you can use it this way
int main()
{
using arr = std::integer_sequence<int, 11, 22, 33, 44, 55>;
for ( const int i : get_evens(arr{}) )
std::cout << i << " " << std::endl;
}
#include <array>
#include <iostream>
static constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
template <typename C, typename P, typename T>
static constexpr void invoke_if(const C& rng, P p, T target) {
for (const auto& elem: rng) {
if (p(elem)){
target(elem);
}
}
}
constexpr bool is_even(int i) {
return i % 2 == 0;
}
template<std::size_t N>
constexpr std::size_t count_evens(const std::array<int, N>& arr)
{
std::size_t cnt = 0;
invoke_if(arr, is_even, [&cnt](auto&&){++cnt;});
return cnt;
}
template<std::size_t cnt, std::size_t N>
static constexpr auto get_evens(const std::array<int, N>& arr) {
std::array<int, cnt> result{};
int idx = 0;
invoke_if(arr, is_even, [&result, &idx](const auto& val){ result[idx++] = val;});
return result;
}
int main() {
// constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
for (const int i:get_evens<count_evens(arr)>(arr)) {
std::cout << i << " " << std::endl;
}
}
this works in g++, but in clang we get a problem because the begin on an array isn't properly constexpr with at least one library. Or maybe g++ violates the standard and clang does not.
template<auto t0, auto...ts>
struct ct_array:
std::array<decltype(t0) const, 1+sizeof...(ts)>,
std::integer_sequence<decltype(t0), t0, ts...>
{
ct_array():std::array<decltype(t0) const, 1+sizeof...(ts)>{{t0, ts...}} {};
};
template<class target, auto X>
struct push;
template<auto X>
struct push<void, X>{using type=ct_array<X>;};
template<auto...elems, auto X>
struct push<ct_array<elems...>, X>{using type=ct_array<elems...,X>;};
template<class target, auto X>
using push_t= typename push<target, X>::type;
template<class target>
struct pop;
template<auto x>
struct pop<ct_array<x>>{using type=void;};
template<auto x0, auto...xs>
struct pop<ct_array<x0, xs...>>{using type=ct_array<xs...>;};
template<class target>
using pop_t=typename pop<target>::type;
template<class lhs, class rhs, class F, class=void>
struct transcribe;
template<class lhs, class rhs, class F>
using transcribe_t = typename transcribe<lhs, rhs, F>::type;
template<auto l0, auto...lhs, class rhs, class F>
struct transcribe<ct_array<l0, lhs...>, rhs, F,
std::enable_if_t<F{}(l0) && sizeof...(lhs)>
>:
transcribe<pop_t<ct_array<l0, lhs...>>, push_t<rhs, l0>, F>
{};
template<auto l0, auto...lhs, class rhs, class F>
struct transcribe<ct_array<l0, lhs...>, rhs, F,
std::enable_if_t<!F{}(l0) && sizeof...(lhs)>
>:
transcribe<pop_t<ct_array<l0, lhs...>>, rhs, F>
{};
template<auto lhs, class rhs, class F>
struct transcribe<ct_array<lhs>, rhs, F, void>
{
using type=std::conditional_t< F{}(lhs), push_t<rhs, lhs>, rhs >;
};
template<class lhs, class F>
using filter_t = transcribe_t<lhs, void, F>;
// C++20
//auto is_even = [](auto i)->bool{ return !(i%2); };
struct is_even_t {
template<class T>
constexpr bool operator()(T i)const{ return !(i%2); }
};
constexpr is_even_t is_even{};
template<auto...is>
static constexpr auto get_evens(ct_array<is...>) {
return filter_t< ct_array<is...>, decltype(is_even) >{};
}
Live example.
Test code:
auto arr = ct_array<11, 22, 33, 44, 55>{};
for (const int i : get_evens(arr)) {
std::cout << i << " " << std::endl;
}

how to initialize a static constexpr member of std::vector<std::string> in c++11?

I'm trying to initialize a static constexpr std::vector of std::strings inside my class Foo. I will later use the address of its elements.
class Foo {
public:
static constexpr std::vector<std::string> a = {"a", "bc", "232"}; // not working, constexpr variable not literal ....
const std::vector<std::string> a = {"a", "bc", "232"}; // this works
}
using c++11, thanks.
I can live with const instead of constexpr. but it's a little bit odd that there's no way to do this
It's good you can live with const but, just for fun, I show you a way to make a better-than-nothing constexpr static member that uses std::array instead of std::vector and (again) std::array instead of std::string.
Unfortunately you're using C++11, so no std::index_sequence/std::make_index_sequence (available starting from C++14) but I add a C++11 substitute in the following full example.
If you know a superior limit for the length of the strings you want use in the constexpr member, say 9 (3 in you example), you can define a fakeString type as follows
using fakeString = std::array<char, 10u>;
Observe that the size of the std::array is max-length plus one (plus the final zero).
Now you can define foo as follows
struct foo
{
static constexpr std::array<fakeString, 3u> a
{{ fs("a"), fs("bc"), fs("232") }};
};
constexpr std::array<fakeString, 3u> foo::a;
where fs() is a constexpr function that return a fakeString given a C-style array of char and uses a fsh() helper functions
The fs() and fsh() functions are as follows
template <std::size_t ... Is, std::size_t N>
constexpr fakeString fsh (indexSequence<Is...> const &, char const (&s)[N])
{ return {{ s[Is]... }}; }
template <std::size_t N>
constexpr fakeString fs (char const (&s)[N])
{ return fsh(makeIndexSequence<N>{}, s); }
Now you can use foo::a as follows
for ( auto const & fakeS : foo::a )
std::cout << fakeS.data() << std::endl;
Observe that you have to call the data() method that return a char *, that is a C-style string.
I repeat: just for fun.
The following is a full compiling C++11 example
#include <array>
#include <iostream>
template <std::size_t...>
struct indexSequence
{ using type = indexSequence; };
template <typename, typename>
struct concatSequences;
template <std::size_t... S1, std::size_t... S2>
struct concatSequences<indexSequence<S1...>, indexSequence<S2...>>
: public indexSequence<S1..., ( sizeof...(S1) + S2 )...>
{ };
template <std::size_t N>
struct makeIndexSequenceH
: public concatSequences<
typename makeIndexSequenceH<(N>>1)>::type,
typename makeIndexSequenceH<N-(N>>1)>::type>::type
{ };
template<>
struct makeIndexSequenceH<0> : public indexSequence<>
{ };
template<>
struct makeIndexSequenceH<1> : public indexSequence<0>
{ };
template <std::size_t N>
using makeIndexSequence = typename makeIndexSequenceH<N>::type;
using fakeString = std::array<char, 10u>;
template <std::size_t ... Is, std::size_t N>
constexpr fakeString fsh (indexSequence<Is...> const &, char const (&s)[N])
{ return {{ s[Is]... }}; }
template <std::size_t N>
constexpr fakeString fs (char const (&s)[N])
{ return fsh(makeIndexSequence<N>{}, s); }
struct foo
{
static constexpr std::array<fakeString, 3u> a
{{ fs("a"), fs("bc"), fs("232") }};
};
constexpr std::array<fakeString, 3u> foo::a;
int main ()
{
for ( auto const & fakeS : foo::a )
std::cout << fakeS.data() << std::endl;
}

constexpr to concatenate two or more char strings

I'm trying to make a constexpr function that will concatenate an arbitrary number of char arrays by working from the following answer by Xeo, which concatenates two char arrays.
https://stackoverflow.com/a/13294458/1128289
#include <array>
template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};
template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2], seq<I1...>, seq<I2...>){
return {{ a1[I1]..., a2[I2]... }};
}
template<unsigned N1, unsigned N2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2]){
return concat(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}
My attempt thus far:
#include <iostream>
#include <array>
template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};
template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
constexpr const std::array<char, N1+N2-1>
concat_impl(
const char (&a1)[N1], const char (&a2)[N2], seq<I1...>, seq<I2...>)
{
return {{ a1[I1]..., a2[I2]... }};
}
template<unsigned N1, unsigned N2>
constexpr const std::array<char, N1+N2-1>
concat(const char (&a1)[N1], const char (&a2)[N2])
{
return concat_impl(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}
template<unsigned N1, unsigned N2, class... Us>
constexpr auto
concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs)
-> std::array<char, N1 + decltype(concat(a2, xs...))::size() - 1>
{
return concat(a1, concat(a2, xs...));
}
int main()
{
auto const s = concat("hi ", "there!");
std::cout << s.data() << std::endl;
// compile error:
auto const t = concat("hi ", "there ", "how ", "are ", "you?");
std::cout << t.data() << std::endl;
}
Both gcc 4.9 and clang 3.5 give errors indicating that no function matching the concat inside the decltype expression can be found.
clang:
error: no matching function for call to 'concat'
auto const t = concat("hi ", "there ", "how ", "are ", "you?");
^~~~~~
ctconcat.cpp:105:16: note: candidate template ignored: substitution failure [with N1 = 4, N2 = 7, Us = <char [5], char [5], char [5]>]: no matching function for call to 'concat'
constexpr auto concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs) -> std::array<char, N1 + decltype(concat(a2, xs...))::size() - 1>
^ ~~~~~~
ctconcat.cpp:62:43: note: candidate function template not viable: requires 2 arguments, but 5 were provided
constexpr const std::array<char, N1+N2-1> concat(const char (&a1)[N1], const char (&a2)[N2])
^
1 error generated.
The errors from gcc and clang both indicate that the second concat function template is not a candidate for the concat in the decltype expression. Only the first template is considered. Why is that and how do I fix this?
Edit: Relevant question on why decltype can't be used recursively
trailing return type using decltype with a variadic template function
template<size_t S>
using size=std::integral_constant<size_t, S>;
template<class T, size_t N>
constexpr size<N> length( T const(&)[N] ) { return {}; }
template<class T, size_t N>
constexpr size<N> length( std::array<T, N> const& ) { return {}; }
template<class T>
using length_t = decltype(length(std::declval<T>()));
constexpr size_t sum_string_sizes() { return 0; }
template<class...Ts>
constexpr size_t sum_string_sizes( size_t i, Ts... ts ) {
return (i?i-1:0) + sum_sizes(ts...);
}
then
template
template<unsigned N1, unsigned N2, class... Us>
constexpr auto
concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs)
-> std::array<char, sum_string_sizes( N1, N2, length_t<Us>::value... )+1 >
{
return concat(a1, concat(a2, xs...));
}
which gets rid of the recursion-in-decltype.
Here is a full example using the above approach:
template<size_t S>
using size=std::integral_constant<size_t, S>;
template<class T, size_t N>
constexpr size<N> length( T const(&)[N] ) { return {}; }
template<class T, size_t N>
constexpr size<N> length( std::array<T, N> const& ) { return {}; }
template<class T>
using length_t = decltype(length(std::declval<T>()));
constexpr size_t string_size() { return 0; }
template<class...Ts>
constexpr size_t string_size( size_t i, Ts... ts ) {
return (i?i-1:0) + string_size(ts...);
}
template<class...Ts>
using string_length=size< string_size( length_t<Ts>{}... )>;
template<class...Ts>
using combined_string = std::array<char, string_length<Ts...>{}+1>;
template<class Lhs, class Rhs, unsigned...I1, unsigned...I2>
constexpr const combined_string<Lhs,Rhs>
concat_impl( Lhs const& lhs, Rhs const& rhs, seq<I1...>, seq<I2...>)
{
// the '\0' adds to symmetry:
return {{ lhs[I1]..., rhs[I2]..., '\0' }};
}
template<class Lhs, class Rhs>
constexpr const combined_string<Lhs,Rhs>
concat(Lhs const& lhs, Rhs const& rhs)
{
return concat_impl(
lhs, rhs,
gen_seq<string_length<Lhs>{}>{},
gen_seq<string_length<Rhs>{}>{}
);
}
template<class T0, class T1, class... Ts>
constexpr const combined_string<T0, T1, Ts...>
concat(T0 const&t0, T1 const&t1, Ts const&...ts)
{
return concat(t0, concat(t1, ts...));
}
template<class T>
constexpr const combined_string<T>
concat(T const&t) {
return concat(t, "");
}
constexpr const combined_string<>
concat() {
return concat("");
}
live example
With C++17 the solution becomes very simple (here's the live version):
#include <initializer_list>
// we cannot return a char array from a function, therefore we need a wrapper
template <unsigned N>
struct String {
char c[N];
};
template<unsigned ...Len>
constexpr auto cat(const char (&...strings)[Len]) {
constexpr unsigned N = (... + Len) - sizeof...(Len);
String<N + 1> result = {};
result.c[N] = '\0';
char* dst = result.c;
for (const char* src : {strings...}) {
for (; *src != '\0'; src++, dst++) {
*dst = *src;
}
}
return result;
}
// can be used to build other constexpr functions
template<unsigned L>
constexpr auto makeCopyright(const char (&author)[L]) {
return cat("\xC2\xA9 ", author);
}
constexpr char one[] = "The desert was the apotheosis of all deserts";
constexpr char two[] = "huge, standing to the sky";
constexpr auto three = cat(
cat(one, ", ", two).c, // can concatenate recursively
" ",
"for what looked like eternity in all directions."); // can use in-place literals
constexpr auto phrase = cat(
three.c, // can reuse existing cats
"\n",
makeCopyright("Stephen King").c);
#include <cstdio>
int main() {
puts(phrase.c);
return 0;
}