Related
Hi I'm using boost::pfr for basic reflection, it works fine, but the problem is it is only print or deal with the field values, like with boost::pfr::io it prints each member of the struct, but how can I print it as name value pairs, same issue with for_each_field, the functor only accepts values, but not names. How can I get the field names?
struct S {
int n;
std::string name;
};
S o{1, "foo"};
std::cout << boost::pfr::io(o);
// Outputs: {1, "foo"}, how can I get n = 1, name = "foo"?
If you think adapting a struct is not too intrusive (it doesn't change your existing definitions, and you don't even need to have it in a public header):
BOOST_FUSION_ADAPT_STRUCT(S, n, name)
Then you can concoct a general operator<< for sequences:
namespace BF = boost::fusion;
template <typename T,
typename Enable = std::enable_if_t<
// BF::traits::is_sequence<T>::type::value>
std::is_same_v<BF::struct_tag, typename BF::traits::tag_of<T>::type>>>
std::ostream& operator<<(std::ostream& os, T const& v)
{
bool first = true;
auto visitor = [&]<size_t I>() {
os << (std::exchange(first, false) ? "" : ", ")
<< BF::extension::struct_member_name<T, I>::call()
<< " = " << BF::at_c<I>(v);
};
// visit members
[&]<size_t... II>(std::index_sequence<II...>)
{
return ((visitor.template operator()<II>(), ...);
}
(std::make_index_sequence<BF::result_of::size<T>::type::value>{});
return os;
}
(Prior to c++20 this would require some explicit template types instead of the lambdas, perhaps making it more readable. I guess I'm lazy...)
Here's a live demo: Live On Compiler Explorer
n = 1, name = foo
Bonus: Correctly quoting string-like types
Live On Compiler Explorer
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/at_c.hpp>
#include <iostream>
#include <iomanip>
namespace MyLib {
struct S {
int n;
std::string name;
};
namespace BF = boost::fusion;
static auto inline pretty(std::string_view sv) { return std::quoted(sv); }
template <typename T,
typename Enable = std::enable_if_t<
not std::is_constructible_v<std::string_view, T const&>>>
static inline T const& pretty(T const& v)
{
return v;
}
template <typename T,
typename Enable = std::enable_if_t<
// BF::traits::is_sequence<T>::type::value>
std::is_same_v<BF::struct_tag, typename BF::traits::tag_of<T>::type>>>
std::ostream& operator<<(std::ostream& os, T const& v)
{
bool first = true;
auto visitor = [&]<size_t I>() {
os << (std::exchange(first, false) ? "" : ", ")
<< BF::extension::struct_member_name<T, I>::call()
<< " = " << pretty(BF::at_c<I>(v));
};
// visit members
[&]<size_t... II>(std::index_sequence<II...>)
{
return (visitor.template operator()<II>(), ...);
}
(std::make_index_sequence<BF::result_of::size<T>::type::value>{});
return os;
}
} // namespace MyLib
BOOST_FUSION_ADAPT_STRUCT(MyLib::S, n, name)
int main()
{
MyLib::S o{1, "foo"};
std::cout << o << "\n";
}
Outputs:
n = 1, name = "foo"
The library cannot offer any such functionality because it is currently impossible to obtain the name of a member of a class as value of an object.
If you want to output field names, you need to declare string objects mapped with the members and implement a operator<< which uses these strings manually.
To do this a more sophisticated reflection library would probably offer macros to use in the definition of the members. Macros can expand their argument(s) into a declaration using the provided name as identifier while also producing code using the name as string literal (via the # macro replacement operator).
It's stupid but hey, with a stringifying macro per field it could be enough for you.
C++14, no additional library
#include <boost/pfr.hpp>
struct S
{
int n;
std::string name;
static char const* const s_memNames[2];
};
char const* const S::s_memNames[2] = {"n", "name"};
// utility
template< size_t I, typename TR >
char const* MemberName()
{
using T = std::remove_reference_t<TR>;
if (I < std::size(T::s_memNames))
return T::s_memNames[I];
return nullptr;
}
// test:
#include <iostream>
using std::cout;
template< size_t I, typename T >
void StreamAt(T&& inst)
{
char const* n = MemberName<I,T>();
auto& v = boost::pfr::get<I>(inst);
cout << "(" << n << " = " << v << ")";
}
int main()
{
S s{2, "boo"};
boost::pfr::for_each_field(s, [&](const auto&, auto I)
{
StreamAt<decltype(I)::value>(s);
cout << "\n";
});
}
output:
(n = 2)
(name = boo)
(previous version of the suggestion, this one has more fluff so less interesting)
#include <boost/pfr.hpp>
// library additions:
static char const* g_names[100];
template< size_t V >
struct Id : std::integral_constant<size_t, V > {};
template< size_t I, typename T >
using TypeAt = boost::pfr::tuple_element_t<I, T>;
template<std::size_t Pos, class Struct>
constexpr int Ni() // name index
{
return std::tuple_element_t<Pos, typename std::remove_reference_t<Struct>::NamesAt >::value;
}
struct StaticCaller
{
template< typename Functor >
StaticCaller(Functor f) { f();}
};
///
/// YOUR CODE HERE
struct S
{
using NamesAt = std::tuple<Id<__COUNTER__>, Id<__COUNTER__>>; // add this
int n;
std::string name;
static void Init() // add this
{
g_names[Ni<0,S>()] = "n";
g_names[Ni<1,S>()] = "name";
}
};
StaticCaller g_sc__LINE__(S::Init); // add this
// utilities
template< size_t I, typename T >
auto GetValueName(T&& inst)
{
return std::make_pair(boost::pfr::get<I>(inst), g_names[Ni<I,T>()]);
}
// test:
#include <iostream>
using std::cout;
template< size_t I, typename T >
void StreamAt(T&& inst)
{
auto const& [v,n] = GetValueName<I>(inst);
cout << "(" << v << ", " << n << ")";
}
int main()
{
S s{2, "boo"};
boost::pfr::for_each_field(s, [&](const auto&, auto I)
{
StreamAt<decltype(I)::value>(s);
cout << "\n";
});
}
output
(2, n)
(boo, name)
I have a code where I want to display tensor represented as vectors of vectors or std::arrays of std::arrays. The intention is to print them the way numpy prints them. I am still learning meta programming in c++ and wanted to explore how to print the n-dim container using function template that can take this container of containers and iterate through it recursively and return a string which I can later cout.
Numpy example:
>>> np.ones([2,2])
array([[1., 1.],
[1., 1.]])
>>> np.ones([2,2,4])
array([[[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.]]])
>>> np.ones(4)
array([1., 1., 1., 1.])
>>>
Tried so far:
I tried the response accepted here as the response:
Print simply STL vectors of vectors recursively in C++
It did work for me for 2 dim vectors but the compilation failed for me with 3d vectors when I changed the call to printContainer() to printContainerV2() inside printContainerV2():
Code:
#include <iostream>
#include <iterator>
#include <vector>
template <typename Iter, typename Cont>
bool isLast(Iter iter, const Cont& cont)
{
return (iter != cont.end()) && (next(iter) == cont.end());
}
template <typename T>
struct is_cont {
static const bool value = false;
};
template <typename T,typename Alloc>
struct is_cont<std::vector<T,Alloc> > {
static const bool value = true;
};
template <typename T>
std::string printContainer(T const& container)
{
std::string str = "{";
for (auto it = std::begin(container); it != std::end(container); ++ it)
if (isLast(it, container))
str = str + std::to_string(*it) + "}";
else
str = str + std::to_string(*it) + ",";
return str;
}
template<typename T>
using if_not_cont = std::enable_if<!is_cont<T>::value, T>;
template<typename T>
using if_cont = std::enable_if<is_cont<T>::value, T>;
template <typename T, typename std::enable_if<!is_cont<T>::value, T>::type* = nullptr>
std::string printContainerV2(T const& container)
{
std::string str = "{";
for (auto it = std::begin(container); it != std::end(container); ++ it)
if (isLast(it, container))
str = str + std::to_string(*it) + "}";
else
str = str + std::to_string(*it) + ",";
return str;
}
template <typename T, typename std::enable_if<is_cont<T>::value, T>::type* = nullptr>
std::string printContainerV2(T const& container)
{
std::string str = "{";
for (auto it = std::begin(container); it != std::end(container); ++ it)
if (isLast(it, container))
str = str + printContainerV2(*it) + "}";
else
str = str + printContainerV2(*it) + ",";
return str;
}
int main()
{
std::vector<int> A({2,3,6,8});
std::vector<std::vector<int>> M(2,A);
std::vector<std::vector<std::vector<float>>> m3{{{1,2}, {3,4}},{{5,6}, {7,8}},{{1,2}, {5,9}}};
std::cout << is_cont<decltype(A)>::value << std::endl; // returns true !
// for (auto it = std::begin(M); it != std::end(M); ++ it)
// {
// std::cout << printContainer(*it) << std::endl; // works well std::vector<int>
// std::cout << is_cont<decltype(*it)>::value << std::endl; // return false :(
// }
// Want to use this for printing a std::vector<std::vector<int>>
std::cout << printContainerV2(M) << std::endl; // not working !
std::cout << printContainerV2(m3) << std::endl; // not working
}
Command: clang++ --std=c++17 test.cpp
test.cpp is the name of the code above.
I got this error:
test.cpp:45:20: error: no matching function for call to 'begin'
for (auto it = std::begin(container); it != std::end(container); ++ it)
^~~~~~~~~~
test.cpp:59:29: note: in instantiation of function template specialization 'printContainerV2<int, nullptr>'
requested here
str = str + printContainerV2(*it) + "}";
^
test.cpp:59:29: note: in instantiation of function template specialization 'printContainerV2<std::__1::vector<int,
std::__1::allocator<int> >, nullptr>' requested here
test.cpp:80:19: note: in instantiation of function template specialization
'printContainerV2<std::__1::vector<std::__1::vector<int, std::__1::allocator<int> >,
std::__1::allocator<std::__1::vector<int, std::__1::allocator<int> > > >, nullptr>' requested here
std::cout << printContainerV2(M) << std::endl; // not working !
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/initializer_list:99:1: note:
candidate template ignored: could not match 'initializer_list<type-parameter-0-0>' against 'int'
begin(initializer_list<_Ep> __il) _NOEXCEPT
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:1753:1: note:
candidate template ignored: could not match '_Tp [_Np]' against 'const int'
begin(_Tp (&__array)[_Np])
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:1771:1: note:
candidate template ignored: substitution failure [with _Cp = const int]: member reference base type
'const int' is not a structure or union
begin(_Cp& __c) -> decltype(__c.begin())
^ ~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:1779:1: note:
candidate template ignored: substitution failure [with _Cp = int]: member reference base type 'const int' is
not a structure or union
begin(const _Cp& __c) -> decltype(__c.begin())
^ ~
1 error generated.
Here is a simple way to print vectors of any dimension:
template<typename T>
std::ostream& operator<<(std::ostream& out, const std::vector<T> &vec){
out << "[ ";
for(const auto& t: vec){
out << t << " ";
}
out << "] ";
return out;
}
For any other container, you can do the exact same thing.
Use:
int main()
{
std::vector<int> v{1,2,3};
std::cout << v << std::endl;
std::vector<std::vector<std::vector<int>>> v_ds
{
{{{1,2,3},{4,5,6}},{{7,8},{9,10}}, {{1,2,3},{4,5,6}},{{7,8},{9,10}}}
};
std::cout << v_ds << std::endl;
return 0;
}
EDIT: Here is a version of operator<< that can print anything that can be iterated over (such as std::array, std::vector, std::list, and even c-arrays of known bounds):
template<typename Container, typename =
std::enable_if_t<std::is_same_v<std::void_t<
decltype(static_cast<typename Container::const_iterator (*)(const Container&)>(&std::cbegin)),
decltype(static_cast<typename Container::const_iterator (*)(const Container&)>(&std::cend))>, void>
&& !std::is_same_v<std::string, Container>>>
std::ostream& operator<<(std::ostream& out, const Container &vec)
{
out << "[ ";
for(const auto& t: vec){
out << t << " ";
}
out << "] ";
return out;
}
Messy, I know, but is such syntactic sugar to use :)
ANOTHER EDIT: The version I posted before was subtly wrong when working with c-arrays. Here is an updated version:
template<class...> constexpr bool true_t = true; // replacement of void_t
template<typename Container>
auto operator<<(std::ostream& out, Container&& vec) ->
std::enable_if_t<
true_t<
decltype(std::cbegin(vec)),
decltype(std::cend(vec))
>
&& !std::is_convertible_v<Container, std::string_view>,
std::ostream&
>
{
out << "[ ";
for(const auto& t: vec){
out << t << " ";
}
out << "] ";
return out;
}
When your first printContainerV2 is enabled, !is_cont<T>::value is true, which means that your T is no longer a container type at this time. In your example, they are int, so you cannot call std::begin on the int, you should directly return std::to_string(value).
template <typename T, typename std::enable_if<!is_cont<T>::value, T>::type* = nullptr>
std::string printContainerV2(T const& value)
{
return std::to_string(value);
}
Demo.
For nested containers in C++17, differentiating types, you could do something like:
#include <bits/stdc++.h>
using namespace std;
template <typename, typename = void> constexpr bool is_iterable{};
template <> constexpr bool is_iterable<string> = false;
template <typename T, size_t N> constexpr bool is_iterable<T[N]> = true;
template <typename T> constexpr bool is_iterable<T, void_t<decltype(declval<T>().begin()), decltype(declval<T>().end())>> = true;
template <typename> constexpr bool is_set{};
template <typename T> constexpr bool is_set<set<T>> = true;
template <typename T, typename U> constexpr bool is_set<map<T, U>> = true;
template <typename> constexpr bool is_tuple{};
template <typename... T> constexpr bool is_tuple<tuple<T...>> = true;
template <typename T, typename U> constexpr bool is_tuple<pair<T, U>> = true;
inline string to_string(string s) { return '"' + s + '"'; }
inline string to_string(char c) { return '\'' + string(1, c) + '\''; }
template<typename T>
string print(const T& t, string&& s = string(), size_t depth = 0) {
constexpr size_t TAB_SPACES = 2;
if constexpr (!is_tuple<T> and !is_iterable<T>) {
if (s.back() == '\n') s.pop_back();
s += to_string(t) + ", ";
} else {
pair<string, string> braces = is_tuple<T> ? pair("(", ")") : is_set<T> ? pair("{", "}") : pair("[", "]");
s += string(TAB_SPACES * depth, ' ') + braces.first + '\n';
if constexpr (is_tuple<T>)
if constexpr (tuple_size_v<T> == 0) s.push_back('\0');
else apply([&s, depth](const auto&... x){ ((s = print(x, move(s), depth + 1)), ...); }, t);
else if (begin(t) == end(t)) s.push_back('\0');
else for_each(begin(t), end(t), [&s, depth](const auto& x){ s = print(x, move(s), depth + 1); });
s.erase(s.length() - 2);
if (s.back() == ')' or s.back() == '}' or s.back() == ']') s += '\n' + string(TAB_SPACES * depth, ' ');
s += braces.second + ",\n";
}
if (depth == 0) cout << s.erase(s.length() - 2) << endl;
return move(s);
}
Then:
vector<map<int, string>> ex = {{{1, "a"}, {2, "b"}}};
print(ex);
Will print:
[
{
(1, "a"),
(2, "b")
}
]
Used for a scripting template where simplicity and no external deps took priority. Production probably better served by a library.
I was writing a CSV parser and I thought it would be a great idea to put in practice some advanced C++. In particular, there's a useful function to split a line of a CSV file given a delimiter. Although it's a straightfoward function to write, now I want that function to return a tuple with a varying number of arguments and types. For example :
int main() {
auto [a, b, c] = extract<int, std::string, float>("42;hello;3.1415", ';');
std::cout << a << ' ' << b << ' ' << c << std::endl;
}
Should print out :
42 hello 3.1415
So I thought of a variadic template function :
template <typename... T>
std::tuple<T...> extract(const std::string&& str, const char&& delimiter) {
std::tuple<T...> splited_line;
/* ... */
return splited_line;
}
But I can't modify the tuple inside that function with a variable parameter, like so :
std::get<i>(splited_line) // doesn't work
That wasn't a big surprise, I'm quite new to this language. I'm now wondering how to achieve this small function in a elegant way.
Thanks for any help.
You might do something like (I let you implement "parsing" part):
// Parsing parts
std::vector<std::string> split(const std::string& s, char delimiter);
template <typename T>
T ConvertTo(const std::string& s);
// Variadic part
template <typename... Ts, std::size_t ... Is>
std::tuple<Ts...> extract_impl(std::index_sequence<Is...>,
const std::vector<std::string>& v)
{
return { ConvertTo<Ts>(v[Is])... };
}
template <typename... Ts>
std::tuple<Ts...> extract(const std::string& s, char delimiter) {
const auto strings = split(s, delimiter);
if (strings.size() != sizeof...(Ts)) {
// Error handling
// ...
}
return extract_impl<Ts...>(std::index_sequence_for<Ts...>(), strings);
}
template<class F>
auto foreach_argument( F&& f ) {
return [f = std::forward<F>(f)](auto&&...elems) {
( (void)f(elems), ... );
};
}
template <class... Ts>
std::tuple<Ts...> extract(const std::string& str, const char delimiter) {
std::tuple<Ts...> splited_line;
std::size_t i = 0;
std::size_t index = 0;
auto operation = [&](auto&& elem){
if (index == std::string::npos)
return;
auto next = str.find( delimiter, index );
std::string element = str.substr( index, next );
index = next;
// parse the string "element" into the argument "elem"
++i;
};
std::apply(foreach_argument(operation), splitted_line);
return splited_line;
}
this results in default-constructed Ts first, and if the element isn't found it remains default-constructed.
The return value
std::optional<std::tuple<Ts...>>
or throw-if-not-matching options would have a
std::tuple<std::optional<Ts>...>
within the function, and the lambda in apply would .emplace the element when it was found. Then ensure that all elements are valid before returning, else throw or return the empty optional.
Ie, to turn a std::tuple<std::optional<Ts>...>> into a std::tuple<Ts...> something like:
return std::apply( [](auto&&elems){ return std::make_tuple( *elems... ); }, splitted_line );
Okay, thanks to the help of the community, I got my problem solved. Maybe it'll help someone understands variadic template functions, so I'm going to share a working code (based on Adam Nevraumont's code) :
#include <iostream>
#include <string>
#include <tuple>
#include <string_view>
#include <sstream>
template <typename... Ts>
std::tuple<Ts...> extract(std::string_view str, char delimiter = ';') {
size_t idx = 0;
auto pop = [&](auto&& elem) {
auto next = str.find(delimiter, idx);
std::stringstream ss;
ss << str.substr(idx, next - idx);
ss >> elem;
idx = next + 1;
};
std::tuple<Ts...> splited;
std::apply([&](auto&&...elems) { (pop(elems), ...); }, splited);
return splited;
}
int main() {
std::string dataline = "-42;hello;3.1415;c";
auto [i, s, f, c] = extract<int, std::string, float, char>(dataline);
std::cout << i << " " << s << " " << f << " " << c << std::endl;
}
As you can see, I convert string into the type I want with stringstream... maybe if you have more control on the type you're handling in the tuple, you have to implement an another template variadic function and then specialize it (based on Jarod42's code) :
#include <iostream>
#include <string>
#include <tuple>
#include <string_view>
template <typename T> T convert_to(const std::string_view& s) { return T(); } // default constructor
template <> std::string convert_to(const std::string_view& s) { return std::string(s); }
template <> float convert_to(const std::string_view& s) { return std::stof(std::string(s)); }
template <> int convert_to(const std::string_view& s) { return std::stoi(std::string(s)); }
template <typename... Ts, size_t... Is>
std::tuple<Ts...> extract_impl(std::index_sequence<Is...>,
std::string_view splited[sizeof...(Ts)]) {
return { convert_to<Ts>(splited[Is])... };
}
template <typename... Ts>
std::tuple<Ts...> extract(std::string_view str, char delimiter = ';') {
std::string_view splited[sizeof...(Ts)];
for (size_t i = 0, idx = 0; i < sizeof...(Ts); ++i) {
auto next = str.find(delimiter, idx);
splited[i] = str.substr(idx, next - idx);
idx = next + 1;
}
return extract_impl<Ts...>(std::index_sequence_for<Ts...>(), splited);
}
int main() {
auto [a, b, c] = extract<int, std::string, float>("-42;hello;3.1415");
std::cout << a << ' ' << b << ' ' << c;
}
This question already has answers here:
How can I print a list of elements separated by commas?
(34 answers)
Closed 2 years ago.
I'm trying to print a comma separated list of a single detail from a std::vector<MyClass>. So far the simplest and cleverest way I have seen to do this is to use
std::ostringstream ss;
std::copy(vec.begin(), vec.end() - 1, std::ostream_iterator<std::string>(ss, ", "))
ss << vec.back();
That worked fine when I was printing a vector of strings. However, now I am trying to print a single detail about MyClass. I know in Python I could do something like
(x.specific_detail for x in vec)
to get a generator expression for the thing that I am interested in. I'm wondering if I can do something similar here or if I am stuck doing
for (auto it = vec.begin(); it != vec.end(); ++it) {
// Do stuff here
}
One way of solving this I have seen is:
std::string separator;
for (auto x : vec) {
ss << separator << x.specific_detail;
separator = ",";
}
A fairly easy and reusable way:
#include <vector>
#include <iostream>
template<class Stream, class T, class A>
Stream& printem(Stream&os, std::vector<T, A> const& v)
{
auto emit = [&os, need_comma = false](T const& x) mutable
{
if (need_comma) os << ", ";
os << x;
need_comma = true;
};
for(T const& x : v) emit(x);
return os;
}
int main()
{
auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
printem(std::cout, v) << std::endl;
}
And another way which defines an extendable protocol for printing containers:
#include <vector>
#include <iostream>
template<class Container>
struct container_printer;
// specialise for a class of container
template<class T, class A>
struct container_printer<std::vector<T, A>>
{
using container_type = std::vector<T, A>;
container_printer(container_type const& c) : c(c) {}
std::ostream& operator()(std::ostream& os) const
{
const char* sep = "";
for (const T& x : c) {
os << sep << x;
sep = ", ";
}
return os;
}
friend std::ostream& operator<<(std::ostream& os, container_printer const& cp)
{
return cp(os);
}
container_type c;
};
template<class Container>
auto print_container(Container&& c)
{
using container_type = typename std::decay<Container>::type;
return container_printer<container_type>(c);
}
int main()
{
auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
std::cout << print_container(v) << std::endl;
}
...of course we can go further...
#include <vector>
#include <iostream>
template<class...Stuff>
struct container_printer;
// specialise for a class of container
template<class T, class A, class Separator, class Gap, class Prefix, class Postfix>
struct container_printer<std::vector<T, A>, Separator, Gap, Prefix, Postfix>
{
using container_type = std::vector<T, A>;
container_printer(container_type const& c, Separator sep, Gap gap, Prefix prefix, Postfix postfix)
: c(c)
, separator(sep)
, gap(gap)
, prefix(prefix)
, postfix(postfix) {}
std::ostream& operator()(std::ostream& os) const
{
Separator sep = gap;
os << prefix;
for (const T& x : c) {
os << sep << x;
sep = separator;
}
return os << gap << postfix;
}
friend std::ostream& operator<<(std::ostream& os, container_printer const& cp)
{
return cp(os);
}
container_type c;
Separator separator;
Gap gap;
Prefix prefix;
Postfix postfix;
};
template<class Container, class Sep = char, class Gap = Sep, class Prefix = char, class Postfix = char>
auto print_container(Container&& c, Sep sep = ',', Gap gap = ' ', Prefix prefix = '[', Postfix postfix = ']')
{
using container_type = typename std::decay<Container>::type;
return container_printer<container_type, Sep, Gap, Prefix, Postfix>(c, sep, gap, prefix, postfix);
}
int main()
{
auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
// json-style
std::cout << print_container(v) << std::endl;
// custom
std::cout << print_container(v, " : ", " ", "(", ")") << std::endl;
// custom
std::cout << print_container(v, "-", "", ">>>", "<<<") << std::endl;
}
expected output:
[ 1,2,3,4,5 ]
( 1 : 2 : 3 : 4 : 5 )
>>>1-2-3-4-5<<<
Here's an example using std::transform:
#include <vector>
#include <string>
#include <iterator>
#include <algorithm>
#include <iostream>
int main()
{
std::vector<std::string> strs = {"Testing", "One", "Two", "Three"};
if (!strs.empty())
{
std::copy(std::begin(strs), std::prev(std::end(strs)), std::ostream_iterator<std::string>(std::cout, ", "));
std::cout << strs.back();
}
std::cout << '\n';
if (!strs.empty())
{
std::transform(std::begin(strs), std::prev(std::end(strs)), std::ostream_iterator<size_t>(std::cout, ", "),
[](const std::string& str) { return str.size(); });
std::cout << strs.back().size();
}
std::cout << '\n';
}
Output:
Testing, One, Two, Three
7, 3, 3, 5
Here is a tiny simple range library:
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
bool empty() const { return begin()==end(); }
std::size_t size() const { return std::distance( begin(), end() ); }
range_t without_front( std::size_t n = 1 ) const {
n = (std::min)(size(), n);
return {std::next(b, n), e};
}
range_t without_back( std::size_t n = 1 ) const {
n = (std::min)(size(), n);
return {b, std::prev(e, n)};
}
range_t only_front( std::size_t n = 1 ) const {
n = (std::min)(size(), n);
return {b, std::next(b, n)};
}
range_t only_back( std::size_t n = 1 ) const {
n = (std::min)(size(), n);
return {std::prev(end(), n), end()};
}
};
template<class It>
range_t<It> range(It s, It f) { return {s,f}; }
template<class C>
auto range(C&& c) {
using std::begin; using std::end;
return range( begin(c), end(c) );
}
now we are ready.
auto r = range(vec);
for (auto& front: r.only_front()) {
std::cout << front.x;
}
for (auto& rest: r.without_front()) {
std::cout << "," << rest.x;
}
Live example.
Now you can get fancier. boost transform iterators, together with boost range, let you do something similar to a list comprehension in python. Or Rangesv3 library for C++2a.
Writing a transform input iterator isn't amazingly hard, it is just a bunch of boilerplate. Simply look at the axioms of input iterator, write a type that stores an arbitrary iterator and forwards most methods to it.
It also stores some function. On * and ->, call the function on the dereferenced iterator.
template<class It, class F>
struct transform_iterator_t {
using reference=std::result_of_t<F const&(typename std::iterator_traits<It>::reference)>;
using value_type=reference;
using difference_type=std::ptrdiff_t;
using pointer=value_type*;
using iterator_category=std::input_iterator_tag;
using self=transform_iterator_t;
It it;
F f;
friend bool operator!=( self const& lhs, self const& rhs ) {
return lhs.it != rhs.it;
}
friend bool operator==( self const& lhs, self const& rhs ) {
return !(lhs!=rhs);
}
self& operator++() {
++it;
return *this;
}
self operator++(int) {
auto r = *this;
++*this;
return r;
}
reference operator*() const {
return f(*it);
}
pointer operator->() const {
// dangerous
return std::addressof( **this );
}
};
template<class F>
auto iterator_transformer( F&& f ) {
return [f=std::forward<F>(f)](auto it){
return transform_iterator_t<decltype(it), std::decay_t<decltype(f)>>{
std::move(it), f
};
};
}
template<class F>
auto range_transfromer( F&& f ) {
auto t = iterator_transformer(std::forward<F>(f));
return [t=std::move(t)](auto&&...args){
auto tmp = range( decltype(args)(args)... );
return range( t(tmp.begin()), t(tmp.end()) );
};
}
Live example of transformer.
And if we add -- we can even use ostream iterator.
Note that std::prev requires a bidirectional iterator, which requires forward iterator concept, which requires that the transform iterator return an actual reference, which is a pain.
You can use the exact code you already have, just change the type you pass to std::ostream_iterator to restrict its output:
class MyClassDetail {
const MyClass &m_cls;
public:
MyClassDetail(const MyClass &src) : m_cls(src) {}
friend std::ostream& operator<<(std::ostream &out, const MyClassDetail &in) {
return out << in.m_cls.specific_detail;
}
};
std::copy(vec.begin(), vec.end()-1, std::ostream_iterator<MyClassDetail>(ss, ", "));
ss << MyClassDetail(vec.back());
Live demo
Here's what was ultimately used
// assume std::vector<MyClass> vec
std::ostringstream ss;
std::for_each(vec.begin(), vec.end() - 1,
[&ss] (MyClass &item) {
ss << item.specific_detail << ", ";
}
);
ss << vec.back().specific_detail;
You can simply the exact same code, but define a operator<< overload:
ostream &operator<<(ostream& out)
{
out << m_detail;
}
I'm creating a logging function that on the one hand needs to be able to parse any number of parameters, but on the other hand will always be passed with __func__ and this(of the calling object).
If I use only variadic templates like this
template<typename... Args>
void
log_device_message_template(const class *object,
char const *func,
char const *const format,
Args const &... args)noexcept
{
log(format, to_str(object).c_str(),format, to_str(args).c_str()...);
}
I have to call log_device_message_template like this :
log_device_message_template(this,__func__,some format,some parameters)
so i've added the macro:
#define log_api_call(format, ...) \
log_device_message_template( \
this, __func__, "%s::%s(" format ")", ##__VA_ARGS__)
The thing is that i'm getting a seg fault, probably due to a bad formatting somewhere. adding __attribiute__(format) doesn't work due to the use of variadic template...
here is the error from the python test testing the logger:
lookup in file=***** [0]
28371: symbol=_ZN5***6logger27log_device_message_templateIINS_14*****E13****EEEvNS_21logger_component_eENS_17logger_level_eEPKcPKNS_9objectES7_DpRT_; lookup in file=**** [0]
28371: ****: error: symbol lookup error: undefined symbol: _ZN5***6logger27log_device_message_templateIINS_14****E13****EEEvNS_21logger_component_eENS_17logger_level_eEPKcPKNS_9objectES7_DpRT_ (fatal)
Here's another way to approach it which neatly avoids macros.
Note in this case I am emitting log records to stdout, but that can easily be altered. I am also using std::ostringstream to build the log message. It's not renowned for performance, so is a good candidate for a customisation point.
However, this should get you started if you like the approach:
#include <iostream>
#include <sstream>
#include <tuple>
#include <utility>
// define a constant index type
template<std::size_t N> using index_c = std::integral_constant<std::size_t, N>;
// emitting an item at index 0 has no prefix
template<class T>
void emit_item(std::ostream &os, index_c<0>, T const &t)
{
os << t;
}
// emitting an item at index N (!= 0) has a comma prefix
template<std::size_t N, class T>
void emit_item(std::ostream &os, index_c<N>, T const &t)
{
os << ", " << t;
}
// emit args 0 .. N-1
template<class Tuple, std::size_t...Is>
void emit_arglist(std::ostream &sink, Tuple &&tuple, std::index_sequence<Is...>)
{
using expand = int[];
void(expand{0,
(emit_item(sink, index_c<Is>(), std::get<Is>(tuple)), 0)...
});
};
// emitting a 'more' at index 0 has a prefix
template<class T>
void emit_more(std::ostream &os, index_c<0>, T const &t)
{
os << " : " << t;
}
// emitting a 'more' at index N (!= 0) has a space prefix
template<std::size_t N, class T>
void emit_more(std::ostream &os, index_c<N>, T const &t)
{
os << " " << t;
}
template<class Tuple, std::size_t...Is>
void emit_more(std::ostream &sink, Tuple &&tuple, std::index_sequence<Is...>)
{
using expand = int[];
void(expand{0,
(emit_more(sink, index_c<Is>(), std::get<Is>(tuple)), 0)...
});
};
template<typename... Args, typename ...MoreStuff>
std::string
make_log_string(const char *object,
char const *func,
std::tuple<Args const &...> const& args,
MoreStuff&&...morestuff) noexcept
{
std::ostringstream ss;
ss << object << "::" << func << '(';
emit_arglist(ss, args, std::make_index_sequence<sizeof...(Args)>());
ss << ')';
emit_more(ss, std::tie(morestuff...), std::make_index_sequence<sizeof...(MoreStuff)>());
return ss.str();
}
// syntactic sugar for indicating arguments
template<class...Arg>
decltype(auto) args(Arg const&...args)
{
return std::tie(args...);
}
int main()
{
int a = 0, b = 1, c = 2;
std::string sa = "xxx", sb = "yyy", sc = "zzz";
const char* Class = "foo";
const char* Func = "var";
std::cout << make_log_string(Class, Func, args(a, b, c)) << std::endl;
std::cout << make_log_string(Class, Func, args(sa, b, sc)) << std::endl;
std::cout << make_log_string(Class, Func, args(sa, b, sc), "more stuff") << std::endl;
std::cout << make_log_string(Class, Func, args(), "empty", "argument", "list") << std::endl;
}
expected output:
foo::var(0, 1, 2)
foo::var(xxx, 1, zzz)
foo::var(xxx, 1, zzz) : more stuff
foo::var() : empty argument list
And with a bit more boilerplate we can write this:
std::cout << make_log_string(method(Class, Func)(a, b, c)) << std::endl;
std::cout << make_log_string(method(Class, Func)(sa, b, sc)) << std::endl;
std::cout << make_log_string(method(Class, Func)(sa, b, sc), "more stuff") << std::endl;
std::cout << make_log_string(method(Class, Func)(), "empty", "argument", "list") << std::endl;
Here it is:
#include <iostream>
#include <sstream>
#include <tuple>
#include <utility>
template<std::size_t N> using index_c = std::integral_constant<std::size_t, N>;
template<class T>
void emit_item(std::ostream &os, index_c<0>, T const &t)
{
os << t;
}
template<std::size_t N, class T>
void emit_item(std::ostream &os, index_c<N>, T const &t)
{
os << ", " << t;
}
template<class Tuple, std::size_t...Is>
void emit_arglist(std::ostream &sink, Tuple &&tuple, std::index_sequence<Is...>)
{
using expand = int[];
void(expand{0,
(emit_item(sink, index_c<Is>(), std::get<Is>(tuple)), 0)...
});
};
template<class T>
void emit_more(std::ostream &os, index_c<0>, T const &t)
{
os << " : " << t;
}
template<std::size_t N, class T>
void emit_more(std::ostream &os, index_c<N>, T const &t)
{
os << " " << t;
}
template<class Tuple, std::size_t...Is>
void emit_more(std::ostream &sink, Tuple &&tuple, std::index_sequence<Is...>)
{
using expand = int[];
void(expand{0,
(emit_more(sink, index_c<Is>(), std::get<Is>(tuple)), 0)...
});
};
template<class...Args>
struct method_with_args;
struct method
{
constexpr method(const char* c, const char* f) : klass(c), func(f) {}
const char* klass;
const char* func;
template<class...Args>
auto operator()(Args const&...args) -> method_with_args<Args...>;
friend std::ostream& operator<<(std::ostream& os, const method& m)
{
return os << m.klass << "::" << m.func;
}
};
template<class...Args>
struct method_with_args
{
friend std::ostream& operator<<(std::ostream& os, method_with_args const& ma)
{
os << ma.m << '(';
emit_arglist(os, ma.args, std::make_index_sequence<sizeof...(Args)>());
return os << ')';
}
method m;
std::tuple<Args const&...> args;
};
template<class...Args>
auto method::operator()(Args const&...args) -> method_with_args<Args...>
{
return method_with_args<Args...>{*this, std::tie(args...)};
}
struct function
{
const char* name;
};
template<typename Method, typename ...MoreStuff>
std::string
make_log_string(Method m,
MoreStuff &&...morestuff) noexcept
{
std::ostringstream ss;
ss << m;
emit_more(ss, std::tie(morestuff...), std::make_index_sequence<sizeof...(MoreStuff)>());
return ss.str();
}
int main()
{
int a = 0, b = 1, c = 2;
std::string sa = "xxx", sb = "yyy", sc = "zzz";
const char *Class = "foo";
const char *Func = "var";
std::cout << make_log_string(method(Class, Func)(a, b, c)) << std::endl;
std::cout << make_log_string(method(Class, Func)(sa, b, sc)) << std::endl;
std::cout << make_log_string(method(Class, Func)(sa, b, sc), "more stuff") << std::endl;
std::cout << make_log_string(method(Class, Func)(), "empty", "argument", "list") << std::endl;
}