Because I am working on a mikrocontroller, where I don't have std::cout available, I want to define several operator<<() functions for my print class.
Because I have several print-backend-classes (one for each output channel e.g. uart, wireless, aso.), I wanted to define my operator<<() functions as templates.
But if I want to call this overloads, they don't work. I get compiler errors like
no match for ‘operator<<’ (operand types are ‘print_t’ and ‘gsl::basic_string_span<const char, -1>’)
I have the following Example:
#include <gsl/string_span>
template <typename T=char, std::size_t N>
constexpr gsl::cstring_span<> gen_cstring_span(const char (&a)[N]) noexcept {
return gsl::cstring_span<>(a, N);
}
class print_t {
public:
inline void append(const gsl::cstring_span<> buf) noexcept {
for(auto c : buf) {
printf("%c", c);
}
}
};
print_t p;
class prefix_t {
public:
static constexpr char prefix[] = "Prefix: ";
template<typename T>
print_t& operator<<(T&& arg) noexcept {
fprintf(stderr, "fwd, ");
p.append(prefix);
p << std::forward<T>(arg);
return p;
}
};
template<typename T>
using span_append_function_exists = std::is_member_function_pointer<decltype(static_cast<void(T::*)(const gsl::cstring_span<>)noexcept>(&T::append))>;
template<typename stream_t, std::enable_if_t<
span_append_function_exists<stream_t>::value, bool>>
stream_t& operator<<(stream_t& sb, const gsl::cstring_span<> str) noexcept {
sb.append(str);
fprintf(stderr, "cstring_span, ");
return sb;
}
template<typename stream_t, typename T=char, std::size_t N>
stream_t& operator<<(stream_t& sb, const char (&str)[N]) noexcept {
fprintf(stderr, "string_literal, ");
auto s = gen_cstring_span(str);
return sb << s;
}
template<typename stream_t>
stream_t& operator<<(stream_t& sb, const bool flag) noexcept {
fprintf(stderr, "bool, ");
if(flag) {
return sb << gen_cstring_span("true");
} else {
return sb << gen_cstring_span("false");
}
}
prefix_t pre;
int main() {
gsl::cstring_span<> span{gen_cstring_span("span")};
p << span << "\n";fprintf(stderr, "\n");
p << "string-literal" << "\n";fprintf(stderr, "\n");
p << true << "\n";fprintf(stderr, "\n");
pre << span << "\n";fprintf(stderr, "\n");
pre << "string-literal" << "\n";fprintf(stderr, "\n");
pre << true << "\n";fprintf(stderr, "\n");
}
This example should be C++17 (and was designed to work on a classic pc, but it should be ported to the MC.) and in this example, the print-backend-class would be print_t.
And each of the operator<<() functions has a template-parameter stream_t, which should become in this example the backend-class print_t.
I have worked with std::enable_if<>, to enable the sream_t param only on classes where the append() function is defined, but I did not succeed well.
Additionally, If I apply the operator on class prefix_t, it should print everything to the backend and additionally add a prefix-value before the output.
The expected STDOUT:
span
string-literal
true
Prefix: span
Prefix: string-literal
Prefix: true
The expected STDERR:
cstring_span,
string_literal, cstring_span,
bool, cstring_span,
fwd, cstring_span,
fwd, string_literal, cstring_span,
fwd, bool, cstring_span,
And a short part of the compiler errors:
tmp3.cpp: In function ‘int main()’:
tmp3.cpp:65:7: error: no match for ‘operator<<’ (operand types are ‘print_t’ and ‘gsl::cstring_span<> {aka gsl::basic_string_span<const char, -1>}’)
p << span << "\n";fprintf(stderr, "\n");
~~^~~~~~~
tmp3.cpp:36:11: note: candidate: template<class stream_t, typename std::enable_if<std::is_member_function_pointer<decltype (static_cast<void (T::*)(gsl::basic_string_span<const char, -1>) noexcept>(& T:: append))>::value, bool>::type <anonymous> > stream_t& operator<<(stream_t&, gsl::cstring_span<>)
stream_t& operator<<(stream_t& sb, const gsl::cstring_span<> str) noexcept {
^~~~~~~~
tmp3.cpp:36:11: note: template argument deduction/substitution failed:
tmp3.cpp:65:10: note: couldn't deduce template parameter ‘<anonymous>’
p << span << "\n";fprintf(stderr, "\n");
^~~~
tmp3.cpp:43:11: note: candidate: template<class stream_t, class T, long unsigned int N> stream_t& operator<<(stream_t&, const char (&)[N])
stream_t& operator<<(stream_t& sb, const char (&str)[N]) noexcept {
^~~~~~~~
tmp3.cpp:43:11: note: template argument deduction/substitution failed:
tmp3.cpp:65:10: note: mismatched types ‘const char [N]’ and ‘gsl::cstring_span<> {aka gsl::basic_string_span<const char, -1>}’
p << span << "\n";fprintf(stderr, "\n");
^~~~
tmp3.cpp:50:11: note: candidate: template<class stream_t> stream_t& operator<<(stream_t&, bool)
stream_t& operator<<(stream_t& sb, const bool flag) noexcept {
^~~~~~~~
tmp3.cpp:50:11: note: template argument deduction/substitution failed:
tmp3.cpp:65:10: note: cannot convert ‘span’ (type ‘gsl::cstring_span<> {aka gsl::basic_string_span<const char, -1>}’) to type ‘bool’
p << span << "\n";fprintf(stderr, "\n");
I am using C++17 with g++7.3.
Related
I am trying to build an operator << overload to print all the standard library container types. So far, the overload works well, but when I use it in a generic program, it breaks when I try to print a simple std::string. In fact, this program:
#include <iostream>
#include <utility>
#include <vector>
#include <map>
#include <string>
// Helper Function to Print Test Containers (Vector and Map)
template <typename T, typename U>
inline std::ostream& operator<<(std::ostream& out, const std::pair<T, U>& p) {
out << "[" << p.first << ", " << p.second << "]";
return out;
}
template <template <typename, typename...> class ContainerType, typename
ValueType, typename... Args>
std::ostream& operator <<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
for (const auto& v : c) {
os << v << ' ';
}
return os;
}
int main()
{
std::vector <int> v = { 1,2,3 };
std::cout << v;
std::map <int,int> m = { { 1, 1} , { 2, 2 }, { 3, 3 } };
std::cout << m;
std::string s = "Test";
std::cout << s;
}
Gives me this error:
prove.cpp: In function ‘int main()’:
prove.cpp:32:13: error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’})
32 | std::cout << s;
| ~~~~~~~~~ ^~ ~
| | |
| | std::string {aka std::__cxx11::basic_string<char>}
| std::ostream {aka std::basic_ostream<char>}
prove.cpp:16:15: note: candidate: ‘std::ostream& operator<<(std::ostream&, const ContainerType<ValueType, Args ...>&) [with ContainerType = std::__cxx11::basic_string; ValueType = char; Args = {std::char_traits<char>, std::allocator<char>}; std::ostream = std::basic_ostream<char>]’
16 | std::ostream& operator <<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
| ^~~~~~~~
In file included from /usr/include/c++/9/string:55,
from /usr/include/c++/9/bits/locale_classes.h:40,
from /usr/include/c++/9/bits/ios_base.h:41,
from /usr/include/c++/9/ios:42,
from /usr/include/c++/9/ostream:38,
from /usr/include/c++/9/iostream:39,
from prove.cpp:1:
/usr/include/c++/9/bits/basic_string.h:6419:5: note: candidate: ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::__cxx11::basic_string<_CharT, _Traits, _Allocator>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’
6419 | operator<<(basic_ostream<_CharT, _Traits>& __os,
| ^~~~~~~~
I think that the problem is due to the fact that this overload is used to print also simple std::string objects, but I didn't find any suitable way to solve this so far. Any help? thanks.
Your operator<< overload may match types for which an operator<< overload is already defined. I suggest that you disable it for such types:
#include <type_traits>
template <template <typename, typename...> class ContainerType,
typename ValueType, typename... Args>
std::enable_if_t<!is_streamable_v<ContainerType<ValueType, Args...>>,
std::ostream&>
operator<<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
for (const auto& v : c) {
os << v << ' ';
}
return os;
}
The enable_if_t line uses SFINAE to disable the function for types that are already streamable.
The type trait is_streamable_v that is used above could look like this:
template<class T>
struct is_streamable {
static std::false_type test(...);
template<class U>
static auto test(const U& u) -> decltype(std::declval<std::ostream&>() << u,
std::true_type{});
static constexpr bool value = decltype(test(std::declval<T>()))::value;
};
// helper variable template:
template<class T>
inline constexpr bool is_streamable_v = is_streamable<T>::value;
Your original program should now work as expected:
int main() {
std::vector <int> v = { 1,2,3 };
std::cout << v;
std::map <int,int> m = { { 1, 1} , { 2, 2 }, { 3, 3 } };
std::cout << m;
std::string s = "Test";
std::cout << s;
}
Demo
The issue is that your << is too generic. It matches std::cout << some_string; and potentially other types for which there is already a << overload. The solution is to not provide overloads for types you do not own (unless it is explicitly permitted).
With minimal changes you can refactor your << to be a named function, then write a wrapper:
template <typename T>
struct my_print {
T& t;
};
template <typename T>
std::ostream& operator<<(std::ostream& out,const my_print<T>& mp) {
// call your named funtion template
do_the_printing(out,mp);
return out;
}
The caveat is that you need to explicitly create the instance:
std::cout << my_print(some_stl_container);
Of course you can also let your function template print directly and call it like this
do_the_print(std::cout,some_stl_container);
I am trying to write a variadic function template to calculate the byte size of a struct. This is going to be used in a network programming project I am working on. The first step I came up with this without the variadic template that works:
#include <cstdint>
#include <iostream>
struct Position
{
int32_t x;
int32_t y;
};
template<typename T>
inline std::size_t sizeT() { return sizeof(T); }
template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }
// if I change the definition of Position, I need to remember to change this function
int main(int argc, char* argv[])
{
std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
std::cout << "Position:" << sizeT<Position>() << std::endl;
return 0;
}
I compiled with g++ 9.3.0 on my Ubuntu 20.04 laptop and it worked fine. However if I try to make it a variadic template then I ran into different compilation errors. The point of this variadic template is to be able to write like the following, so that the implementation doesn't depend on the explicit knowledge of x and y's types.
Here is bad code #1
#include <cstdint>
#include <iostream>
struct Position
{
int32_t x;
int32_t y;
};
template<typename T>
inline std::size_t sizeT() { return sizeof(T); }
template<>
inline std::size_t sizeT<Position>()
{
return sizeT<decltype(Position::x), decltype(Position::y)>();
}
template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }
int main(int argc, char* argv[])
{
std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
std::cout << "Position:" << sizeT<Position>() << std::endl;
return 0;
}
I got
error: ambiguous template specialization 'sizeT' for 'std::size_t sizeT()
note: candidate are: 'template std::size_t sizeT()'
note: template<class T, class... Ts> std::size_t sizeT()'
Bad code #2. If I put the variadic template at a different location:
#include <cstdint>
#include <iostream>
struct Position
{
int32_t x;
int32_t y;
};
template<typename T>
inline std::size_t sizeT() { return sizeof(T); }
template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }
template<>
inline std::size_t sizeT<Position>()
{
return sizeT<decltype(Position::x), decltype(Position::y)>();
}
int main(int argc, char* argv[])
{
std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
std::cout << "Position:" << sizeT<Position>() << std::endl;
return 0;
}
I got
error: call of overloaded 'sizeT<int8_t>()' is ambiguous
note candidate: 'std::size_t sizeT() [with T=signed char; std::size_t = long unsigned int]'
note candidate: 'std::size_t sizeT() [with T=signed char; Ts={}; std::size_t = long unsigned int]'
error: call of overloaded 'sizeT()' is ambiguous
note candidate: 'std::size_t sizeT() [with T=Position; std::size_t = long unsigned int]'
note candidate: 'std::size_t sizeT() [with T=Position; Ts={}; std::size_t = long unsigned int]'
I kinda understand case #2 but I don't understand #1. How can I achieve this? Thank you so much in advance.
You don't use variadic...
You might do (C++17)
template <typename ... Ts>
constexpr std::size_t sizeT() { return (0 + ... + sizeof(Ts)); }
template <>
constexpr std::size_t sizeT<Position>()
{
return sizeT<decltype(Position::x), decltype(Position::y)>();
}
Demo
But sizeT<Position, Position> would return 2 * sizeof(Position) instead of 2 * sizeT<Position> (which are identical here).
You might do instead:
template <typename... Ts>
struct tag{};
template <typename T>
constexpr std::size_t sizeT(tag<T>) { return sizeof(T); }
template <typename ... Ts>
constexpr std::size_t sizeT(tag<Ts...>) { return (0 + ... + sizeT(tag<Ts>())); }
template <typename ... Ts>
constexpr std::size_t sizeT(tag<Ts>...) { return (0 + ... + sizeT(tag<Ts>())); }
constexpr std::size_t sizeT(tag<Position>)
{
return sizeT(tag<decltype(Position::x), decltype(Position::y)>());
}
Demo
I'm writing a function that can pour the content of a string vector into variables. Here's what it looks like:
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
template <typename T, int N = 0>
void pour2(std::vector<std::string> const& vals, T& val) {
std::stringstream ss;
ss << vals[N];
ss >> val;
}
template <typename T, typename ...Ts, int N = 0>
void pour2(std::vector<std::string> const& vals, T& val, Ts& ...args) {
std::stringstream ss;
ss << vals[N];
ss >> val;
pour2<Ts..., N+1>(vals, args...);
}
int main() {
std::vector<std::string> info = {"3", "1.5", "/home/tq/playground/"};
int sz;
double val;
std::string dir;
pour2(info, sz, val, dir);
std::cout << "size = " << sz << std::endl;
std::cout << "value = " << val << std::endl;
std::cout << "dir = " << dir << std::endl;
return 0;
}
However, g++-9.2 complains that
test.cpp: In instantiation of ‘void pour2(const std::vector<std::__cxx11::basic_string<char> >&, T&, Ts& ...) [with T = int; Ts = {double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >}; int N = 0]’:
test.cpp:30:26: required from here
test.cpp:18:19: error: no matching function for call to ‘pour2<double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, (0 + 1)>(const std::vector<std::__cxx11::basic_string<char> >&, double&, std::__cxx11::basic_string<char>&)’
18 | pour2<Ts..., N+1>(vals, args...);
| ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
test.cpp:7:6: note: candidate: ‘template<class T, int N> void pour2(const std::vector<std::__cxx11::basic_string<char> >&, T&)’
7 | void pour2(std::vector<std::string> const& vals, T& val) {
| ^~~~~
test.cpp:7:6: note: template argument deduction/substitution failed:
test.cpp:18:19: error: wrong number of template arguments (3, should be at least 1)
18 | pour2<Ts..., N+1>(vals, args...);
| ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
test.cpp:14:6: note: candidate: ‘template<class T, class ... Ts, int N> void pour2(const std::vector<std::__cxx11::basic_string<char> >&, T&, Ts& ...)’
14 | void pour2(std::vector<std::string> const& vals, T& val, Ts& ...args) {
| ^~~~~
test.cpp:14:6: note: template argument deduction/substitution failed:
and clang-9.0.1 says
test.cpp:18:2: error: no matching function for call to 'pour2'
pour2<Ts..., N+1>(vals, args...);
^~~~~~~~~~~~~~~~~
test.cpp:30:2: note: in instantiation of function template specialization 'pour2<int, double, std::__cxx11::basic_string<char> , 0>' requested here
pour2(info, sz, val, dir);
^
test.cpp:14:6: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'Ts'
void pour2(std::vector<std::string> const& vals, T& val, Ts& ...args) {
^
test.cpp:7:6: note: candidate function template not viable: requires 2 arguments, but 3 were provided
void pour2(std::vector<std::string> const& vals, T& val) {
^
1 error generated.
I find that if I move the non-type template parameter to be the first argument, the code can compile and work as expected:
template <int N = 0, typename T>
void pour(std::vector<std::string> const& vals, T& val) {
std::stringstream ss;
ss << vals[N];
ss >> val;
}
template <int N = 0, typename T, typename ...Ts>
void pour(std::vector<std::string> const& vals, T& val, Ts& ...args) {
std::stringstream ss;
ss << vals[N];
ss >> val;
pour<N+1, Ts...>(vals, args...);
}
I just wonder, why it doesn't work in the first case?
If N appears after the parameter pack, N needs to be deduced from the function arguments or have default arguments, and N cannot be deduced by the functions argument.
"In a function template, the template parameter pack may appear earlier in the list provided that all following parameters can be deduced from the function arguments, or have default arguments". - [parameter_pack - cppreference]
I am trying to write a utility function called print(), In which If a primitive type is passed, it just prints it and if a std::vector<int> of primitive type is passed, it must loop through it and print it. I tried for a std::vector of int but failed miserably, primarily because I am not sure if this is allowed in c++. It would be much helpful if you could provide some generalized way to do it for vectors of all primitive types.
#include <iostream>
#include <vector>
#include <type_traits>
void print(std::ostream& out) {}
template <typename T, typename... Args>
void print(std::ostream& out, T type, Args... args)
{
if (std::is_same<T, std::vector<int>>::value) {
for (auto vector_iterator = type.begin();
vector_iterator != type.end(); ++vector_iterator) {
out << (*vector_iterator) << " ";
}
} else {
out << type;
}
print(out, args...);
}
int main()
{
std::vector<int> v {4,5,6};
print(std::cout, "bla", 1, 23.0, v);
return 0;
}
Here is the error that I encountered, if that helps:
util.cc: In instantiation of 'void print(std::ostream&, T, Args ...) [with T = const char*; Args = {int, double, std::vector<int, std::allocator<int> >}; std::ostream = std::basic_ostream<char>]':
util.cc:26:36: required from here
util.cc:11:42: error: request for member 'begin' in 'type', which is of non-class type 'const char*'
for (auto vector_iterator = type.begin();
^
util.cc:12:20: error: request for member 'end' in 'type', which is of non-class type 'const char*'
vector_iterator != type.end(); ++vector_iterator) {
Thanks
If you can compile C++17, you can use if constexpr
if constexpr (std::is_same<T, std::vector<int>>::value)
Before C++17 you have to develop different functions because the type.begin()/type.end() part is compiled also when value is false (also when T isn't a std::vector).
A possible way to "split" the function is the following
template <typename... Args>
void print (std::ostream& out, std::vector<int> const & v, Args... args)
{
for (auto const & i : v)
out << i << ' ';
print(out, args...);
}
template <typename T, typename... Args>
void print (std::ostream& out, T type, Args... args)
{
out << type;
print(out, args...);
}
or, if you want to intercept different std::vector types, you can templatize the type T of the std::vector and the first function become
template <typename T, typename... Args>
void print (std::ostream& out, std::vector<T> const & v, Args... args)
{
for (auto const & i : v)
out << i << ' ';
print(out, args...);
}
I wrote a simple log function by c++0x variadic templates,
but there are templates deduction error,could somebody help me, thanks!
#ifndef SLOG_H
#define SLOG_H
enum LOGLEVEL{INFO, DEBUG, ERROR};
/* static */ const char* g_loglevel[] = {"INFO", "DEBUG", "ERROR"};
template <class T>
void slog_(const T& v) {
std::cout << v;
}
template <class T, class... Args>
void slog_(const T& v, const Args&... args) {
std::cout << v;
slog_(args...);
}
template <class T, class... Args>
void slog(LOGLEVEL level, const Args&... args) {
time_t t;
struct tm tm;
char buf[32];
time(&t);
localtime_r(&t, &tm);
strftime(buf, sizeof(buf), "%F %T", &tm);
std::cout << "[" << g_loglevel[level] << "][" << buf << "] ";
slog_(args...);
}
#endif
In call function:
slog(INFO, "hello, number ", n, ", next is ", n + 1);
then compile error:
main.cpp:6:53: error: no matching function for call to 'slog(LOGLEVEL, const char [15], int&, const char [11], int)'
main.cpp:6:53: note: candidate is:
In file included from main.cpp:2:0:
slog.h:19:6: note: template<class T, class ... Args> void slog(LOGLEVEL, const Args& ...)
slog.h:19:6: note: template argument deduction/substitution failed:
main.cpp:6:53: note: couldn't deduce template parameter 'T'
Thanks!
Change template <class T, class... Args> to template <class... Args> for slog. As the error message says: couldn't deduce template parameter 'T' because you never use it as a function parameter.