The C++20 feature std::source_location is used to capture information about the context in which a function is called.
When I try to use it with a variadic template function, I encountered a problem: I can't see a place to put the source_location parameter.
The following doesn't work because variadic parameters have to be at the end:
// doesn't work
template <typename... Args>
void debug(Args&&... args,
const std::source_location& loc = std::source_location::current());
The following doesn't work either because the caller will be screwed up by the parameter inserted in between:
// doesn't work either, because ...
template <typename... Args>
void debug(const std::source_location& loc = std::source_location::current(),
Args&&... args);
// the caller will get confused
debug(42); // error: cannot convert 42 to std::source_location
I was informed in a comment that std::source_location works seamlessly with variadic templates, but I struggle to figure out how. How can I use std::source_location with variadic template functions?
The first form can be made to work, by adding a deduction guide:
template <typename... Ts>
struct debug
{
debug(Ts&&... ts, const std::source_location& loc = std::source_location::current());
};
template <typename... Ts>
debug(Ts&&...) -> debug<Ts...>;
Test:
int main()
{
debug(5, 'A', 3.14f, "foo");
}
DEMO
If your function has a fixed parameter before the variadiac arguments, like a printf format string, you could wrap that parameter in a struct that captures source_location in its constructor:
struct FormatWithLocation {
const char* value;
std::source_location loc;
FormatWithLocation(const char* s,
const std::source_location& l = std::source_location::current())
: value(s), loc(l) {}
};
template <typename... Args>
void debug(FormatWithLocation fmt, Args&&... args) {
printf("%s:%d] ", fmt.loc.file_name(), fmt.loc.line());
printf(fmt.value, args...);
}
int main() { debug("hello %s\n", "world"); }
Just put your arguments in a tuple, no macro needed.
#include <source_location>
#include <tuple>
template <typename... Args>
void debug(
std::tuple<Args...> args,
const std::source_location& loc = std::source_location::current())
{
std::cout
<< "debug() called from source location "
<< loc.file_name() << ":" << loc.line() << '\n';
}
And this works*.
Technically you could just write:
template <typename T>
void debug(
T arg,
const std::source_location& loc = std::source_location::current())
{
std::cout
<< "debug() called from source location "
<< loc.file_name() << ":" << loc.line() << '\n';
}
but then you'd probably have to jump through some hoops to get the argument types.
* In the linked-to example, I'm using <experimental/source_location> because that's what compilers accept right now. Also, I added some code for printing the argument tuple.
template <typename... Args>
void debug(Args&&... args,
const std::source_location& loc = std::source_location::current());
"works", but requires to specify template arguments as there are non deducible as there are not last:
debug<int>(42);
Demo
Possible (not perfect) alternatives include:
use overloads with hard coded limit (old possible way to "handle" variadic):
// 0 arguments
void debug(const std::source_location& loc = std::source_location::current());
// 1 argument
template <typename T0>
void debug(T0&& t0,
const std::source_location& loc = std::source_location::current());
// 2 arguments
template <typename T0, typename T1>
void debug(T0&& t0, T1&& t1,
const std::source_location& loc = std::source_location::current());
// ...
Demo
to put source_location at first position, without default:
template <typename... Args>
void debug(const std::source_location& loc, Args&&... args);
and
debug(std::source_location::current(), 42);
Demo
similarly to overloads, but just use tuple as group
template <typename Tuple>
void debug(Tuple&& t,
const std::source_location& loc = std::source_location::current());
or
template <typename ... Ts>
void debug(const std::tuple<Ts...>& t,
const std::source_location& loc = std::source_location::current());
with usage
debug(std::make_tuple(42));
Demo
Not a great solution but... what about place the variadic arguments in a std::tuple?
I mean... something as
template <typename... Args>
void debug (std::tuple<Args...> && t_args,
std::source_location const & loc = std::source_location::current());
Unfortunately, this way you have to explicitly call std::make_tuple calling it
debug(std::make_tuple(1, 2l, 3ll));
You can try make it:
#include <iostream>
#include <experimental/source_location>
struct log
{
log(std::experimental::source_location location = std::experimental::source_location::current()) : location { location } {}
template<typename... Args>
void operator() (Args... args)
{
std::cout << location.function_name() << std::endl;
std::cout << location.line() << std::endl;
}
std::experimental::source_location location;
};
int main()
{
log()("asdf");
log()(1);
}
DEMO
If you can accept the use of macros, you can write this to avoid explicitly passing in std::source_ location::current():
template <typename... Args>
void debug(const std::source_location& loc, Args&&... args);
#define debug(...) debug(std::source_location::current() __VA_OPT__(,) __VA_ARGS__)
Related
My workspace is in Windows, Visual Studio 2017. I write two templates to change all str-like to std::wstring. But a complier err comes:
Error C2975
'_Test': invalid template argument for 'std::conditional', expected compile-time constant expression
My code is following:
#include <codecvt>
#include <iostream>
std::wstring to_wide_string(const std::string& input)
{
std::wstring_convert<std::codecvt_utf8<wchar_t> > converter;
return converter.from_bytes(input);
}
std::string to_byte_string(const std::wstring& input)
{
std::wstring_convert<std::codecvt_utf8<wchar_t> > converter;
return converter.to_bytes(input);
}
// a template to check if the Args can be used in F's constructors.
// for example , check<std::string>(3, 'a').value == true
template <class F>
struct check
{
template <class... Args>
constexpr check(Args&&... args) : value(
std::is_void<
decltype(t2<F, Args...>(0, std::forward<Args>(args)...))
>::type::value) {}
bool value;
struct nat {};
template <class G, class... GArgs>
static auto t2(int, GArgs&&... args) -> decltype(G(std::forward<GArgs>(args)...), void());
template <class, class...>
static auto t2(...)->nat;
};
// this template is make all wstr-like to std::string. But I don't finish it yet
template <typename T, bool is_wstring = std::is_same<std::wstring, std::decay_t<T>>::value>
struct to_tstring : std::string
{
};
// I want all str-like can be translated to std::wstring
template <typename T>
struct to_tstring<T, true> : std::wstring
{
template <typename... Args>
// if I change Args&&... to Args..., it will compile succeed
to_tstring(Args&&... args) : to_tstring<T, true>(
std::conditional<
check<std::string>(std::forward<Args>(args)...).value,
std::true_type, std::false_type
>::type(),
std::forward<Args>(args)...
)
{
std::cout << "to_tstring<T, true>::ctor_1" << std::endl;
}
private:
template <typename... Args>
to_tstring(std::false_type, Args&&... args) : std::wstring(std::forward<Args>(args)...) {
std::cout << "to_tstring<T, true>::ctor_2 false_type" << std::endl;
}
template <typename... Args>
to_tstring(std::true_type, Args&&... args) : std::wstring(to_wide_string(std::string(std::forward<Args>(args)...))) {
std::cout << "to_tstring<T, true>::ctor_3 true_type" << std::endl;
}
};
int main()
{
to_tstring<std::wstring> a("asdf"); // compiler error
return 0;
}
I don't know why the std::conditional does not recognize the checkstd::string(std::forward(args)...).value. It's fine to be a constexpr when I used it in main:
int main()
{
constexpr int i = check<std::string>(3,'j').value;
int s[i] = {}; // it's fine
}
By coincidence, I change the signature from to_tstring(Args&&... args) to to_tstring(Args... args), now it is successful to complie. But I don't know why.
The reason I need those template, it's I have a macro called TSTRING. TSTIRNG is std::string in WIN32, std::wstring in x64. Sometimes std::string and std::wstring can be showed at same time, so I want to tranform all the string I meeted to TSTRING
I have a method that accepts format string + arguments (right as printf()), however, I'm using variadic templates for this purpose:
template<typename... Args>
static void log(const char* pszFmt, Args&&... args)
{
doSomething(pszFmt, std::forward<Args>(args)...);
}
Some of args can be std::string instances. Is it possible to make sure that doSomething will never accept std::string, but will always accept const char* instead of each source std::string passed to log()?
In other words, I need a way to forward all the args to doSomething() making all the std::string arguments substituted with what std::string::c_str() returns.
Thanks in advance!
You could define your own "forwarding" method:
template<typename T>
decltype(auto) myForward(T&& t)
{
return t;
}
template<>
decltype(auto) myForward(std::string& t)
{
return t.c_str();
}
template<>
decltype(auto) myForward(std::string&& t)
{
return t.c_str();
}
template<typename... Args>
static void log(const char* pszFmt, Args&&... args)
{
doSomething(pszFmt, myForward<Args>(std::forward<Args>(args))...);
}
C++17 version
You can use SFINAE to achieve this:
#include <iostream>
#include <utility>
#include <string>
template <typename, typename = void>
struct has_c_str : std::false_type {};
template <typename T>
struct has_c_str<T, std::void_t<decltype(&T::c_str)>> : std::is_same<char const*, decltype(std::declval<T>().c_str())>
{};
template <typename StringType,
typename std::enable_if<has_c_str<StringType>::value, StringType>::type* = nullptr>
static void log(const char* pszFmt, StringType const& arg) {
std::cout << "std::string version" << std::endl;
}
template <typename StringType,
typename std::enable_if<!has_c_str<StringType>::value, StringType>::type* = nullptr>
static void log(const char* pszFmt, StringType arg) {
std::cout << "const char * version" << std::endl;
}
template <typename... Args>
static void log(const char* pszFmt, Args&&... args) {
log(pszFmt, std::forward<Args>(args)...);
}
int main() {
log("str", std::string("aa")); // output: std::string version
log("str", "aa"); // output: const char * version
return 0;
}
Full demo here
Here's an alternative solution. If your logger simply prints each argument and doesn't "store" it, then there's no need to perfect-forward the arguments, a simple pass-by-reference will suffice.
In that case you can simply overload or specialize the printer function for various "printable" types.
template <class T>
decltype(auto) printer(T const& t) {
return t;
}
inline const char* printer(std::string const& t) {
return t.c_str();
}
template<typename... Args>
void log(const char* pszFmt, Args const&... args) {
printf(pszFmt, printer(args)...);
}
int main() {
std::string str{"xyz"};
log("%s %s %s\n", "abc", std::string("def"), str);
}
Note: the non-template overload will always be preferred during overload resolution.
The C++20 feature std::source_location is used to capture information about the context in which a function is called.
When I try to use it with a variadic template function, I encountered a problem: I can't see a place to put the source_location parameter.
The following doesn't work because variadic parameters have to be at the end:
// doesn't work
template <typename... Args>
void debug(Args&&... args,
const std::source_location& loc = std::source_location::current());
The following doesn't work either because the caller will be screwed up by the parameter inserted in between:
// doesn't work either, because ...
template <typename... Args>
void debug(const std::source_location& loc = std::source_location::current(),
Args&&... args);
// the caller will get confused
debug(42); // error: cannot convert 42 to std::source_location
I was informed in a comment that std::source_location works seamlessly with variadic templates, but I struggle to figure out how. How can I use std::source_location with variadic template functions?
The first form can be made to work, by adding a deduction guide:
template <typename... Ts>
struct debug
{
debug(Ts&&... ts, const std::source_location& loc = std::source_location::current());
};
template <typename... Ts>
debug(Ts&&...) -> debug<Ts...>;
Test:
int main()
{
debug(5, 'A', 3.14f, "foo");
}
DEMO
If your function has a fixed parameter before the variadiac arguments, like a printf format string, you could wrap that parameter in a struct that captures source_location in its constructor:
struct FormatWithLocation {
const char* value;
std::source_location loc;
FormatWithLocation(const char* s,
const std::source_location& l = std::source_location::current())
: value(s), loc(l) {}
};
template <typename... Args>
void debug(FormatWithLocation fmt, Args&&... args) {
printf("%s:%d] ", fmt.loc.file_name(), fmt.loc.line());
printf(fmt.value, args...);
}
int main() { debug("hello %s\n", "world"); }
Just put your arguments in a tuple, no macro needed.
#include <source_location>
#include <tuple>
template <typename... Args>
void debug(
std::tuple<Args...> args,
const std::source_location& loc = std::source_location::current())
{
std::cout
<< "debug() called from source location "
<< loc.file_name() << ":" << loc.line() << '\n';
}
And this works*.
Technically you could just write:
template <typename T>
void debug(
T arg,
const std::source_location& loc = std::source_location::current())
{
std::cout
<< "debug() called from source location "
<< loc.file_name() << ":" << loc.line() << '\n';
}
but then you'd probably have to jump through some hoops to get the argument types.
* In the linked-to example, I'm using <experimental/source_location> because that's what compilers accept right now. Also, I added some code for printing the argument tuple.
template <typename... Args>
void debug(Args&&... args,
const std::source_location& loc = std::source_location::current());
"works", but requires to specify template arguments as there are non deducible as there are not last:
debug<int>(42);
Demo
Possible (not perfect) alternatives include:
use overloads with hard coded limit (old possible way to "handle" variadic):
// 0 arguments
void debug(const std::source_location& loc = std::source_location::current());
// 1 argument
template <typename T0>
void debug(T0&& t0,
const std::source_location& loc = std::source_location::current());
// 2 arguments
template <typename T0, typename T1>
void debug(T0&& t0, T1&& t1,
const std::source_location& loc = std::source_location::current());
// ...
Demo
to put source_location at first position, without default:
template <typename... Args>
void debug(const std::source_location& loc, Args&&... args);
and
debug(std::source_location::current(), 42);
Demo
similarly to overloads, but just use tuple as group
template <typename Tuple>
void debug(Tuple&& t,
const std::source_location& loc = std::source_location::current());
or
template <typename ... Ts>
void debug(const std::tuple<Ts...>& t,
const std::source_location& loc = std::source_location::current());
with usage
debug(std::make_tuple(42));
Demo
Not a great solution but... what about place the variadic arguments in a std::tuple?
I mean... something as
template <typename... Args>
void debug (std::tuple<Args...> && t_args,
std::source_location const & loc = std::source_location::current());
Unfortunately, this way you have to explicitly call std::make_tuple calling it
debug(std::make_tuple(1, 2l, 3ll));
You can try make it:
#include <iostream>
#include <experimental/source_location>
struct log
{
log(std::experimental::source_location location = std::experimental::source_location::current()) : location { location } {}
template<typename... Args>
void operator() (Args... args)
{
std::cout << location.function_name() << std::endl;
std::cout << location.line() << std::endl;
}
std::experimental::source_location location;
};
int main()
{
log()("asdf");
log()(1);
}
DEMO
If you can accept the use of macros, you can write this to avoid explicitly passing in std::source_ location::current():
template <typename... Args>
void debug(const std::source_location& loc, Args&&... args);
#define debug(...) debug(std::source_location::current() __VA_OPT__(,) __VA_ARGS__)
Desired behavior
What I basically want is to create a function like this:
void func(std::string_view... args)
{
(std::cout << ... << args);
}
It should be able to work only with classes that are convertible to std::string_view.
Example:
int main()
{
const char* tmp1 = "Hello ";
const std::string tmp2 = "World";
const std::string_view tmp3 = "!";
func(tmp1, tmp2, tmp3, "\n");
return 0;
}
should print: Hello World!
Accomplished behavior
So far, I got here:
template<typename... types>
using are_strings = std::conjunction<std::is_convertible<types, std::string_view>...>;
template<typename... strings, class = std::enable_if_t<are_strings<strings...>::value, void>>
void func(strings... args)
{
(std::cout << ... << args);
}
int main()
{
const char* tmp1 = "Hello ";
const std::string tmp2 = "World";
const std::string_view tmp3 = "!";
func(tmp1, tmp2, tmp3, "\n");
return 0;
}
This actually works as expected, but there is still one big problem.
Problem
Only classes that are convertible to std::string_view can be used in this function and that's great.
However, even though classes are convertible, they are not converted to std::string_view!
This leads to needless copying of data(for example when std::string is passed as argument).
Question
Is there a way to force implicit conversion of variadic arguments to std::string_view?
Note
I know about std::initializer_list, but I would like to keep function call simple, without {}.
namespace impl{
template<class...SVs>
void func(SVs... svs){
static_assert( (std::is_same< SVs, std::string_view >{} && ...) );
// your code here
}
}
template<class...Ts,
std::enable_if_t< (std::is_convertible<Ts, std::string_view >{}&&...), bool > =true
>
void func( Ts&&...ts ){
return impl::func( std::string_view{std::forward<Ts>(ts)}... );
}
or somesuch.
#include <string_view>
#include <utility>
template <typename>
using string_view_t = std::string_view;
template <typename... Ts>
void func_impl(string_view_t<Ts>... args)
{
(std::cout << ... << args);
}
template <typename... Ts>
auto func(Ts&&... ts)
-> decltype(func_impl<Ts...>(std::forward<Ts>(ts)...))
{
return func_impl<Ts...>(std::forward<Ts>(ts)...);
}
DEMO
If you simply want to avoid needless copying of data, use a forward reference and then perform explicit casts (if still required). This way no data is copied but forwarded (in your main.cpp example, all params are passed as const references)
template <typename... strings,
class = std::enable_if_t<are_strings<strings...>::value, void>>
void func(strings&&... args) {
(std::cout << ... << std::string_view{args});
}
Not exactly what you asked... but if you can set a superior limit for a the length of args... (9 in following example) I propose the following solution: a foo<N> struct that inherit N func() static function that accepting 0, 1, 2, ..., N std::string_view.
This way, func() function are accepting what is convertible to std::string_view and all argument are converted to std::string_view.
That is exactly
void func(std::string_view... args)
{ (std::cout << ... << args); }
with the difference that func() functions are static methods inside foo<N>, that there is a limit in args... length and that there is a func() method for every supported length.
The full example is the following.
#include <string>
#include <utility>
#include <iostream>
#include <type_traits>
template <std::size_t ... Is>
constexpr auto getIndexSequence (std::index_sequence<Is...> is)
-> decltype(is);
template <std::size_t N>
using IndSeqFrom = decltype(getIndexSequence(std::make_index_sequence<N>{}));
template <typename T, std::size_t>
struct getType
{ using type = T; };
template <typename, typename>
struct bar;
template <typename T, std::size_t ... Is>
struct bar<T, std::index_sequence<Is...>>
{
static void func (typename getType<T, Is>::type ... args)
{ (std::cout << ... << args); }
};
template <std::size_t N, typename = std::string_view,
typename = IndSeqFrom<N>>
struct foo;
template <std::size_t N, typename T, std::size_t ... Is>
struct foo<N, T, std::index_sequence<Is...>> : public bar<T, IndSeqFrom<Is>>...
{ using bar<T, IndSeqFrom<Is>>::func ...; };
int main ()
{
const char* tmp1 = "Hello ";
const std::string tmp2 = "World";
const std::string_view tmp3 = "!";
foo<10u>::func(tmp1, tmp2, tmp3, "\n");
}
Make it a two-stage production:
template <class... Args>
std::enable_if_t<... && std::is_same<Args, std::string_view>()>
func(Args... args)
{
(std::cout << ... << args);
}
template <class... Args>
auto func(Args&&... args)
-> std::enable_if_t<... || !std::is_same<std::decay_t<Args>, std::string_view>(),
decltype(func(std::string_view(std::forward<Args>(args))...))>
{
func(std::string_view(std::forward<Args>(args))...);
}
I want to be able to combine multiple different arguments into a single string using ostringstream. That way I can log the resulting single string without any random issues.
I got this far:
template <typename T>
void MagicLog(T t)
{
std::cout << t << std::endl;
}
template<typename T, typename... Args>
void MagicLog(T t, Args... args) // recursive variadic function
{
std::cout << t << std::endl;
MagicLog(args...);
}
template<typename T, typename... Args>
void LogAll(int logType, T t, Args... args)
{
std::ostringstream oss;
MagicLog(t);
MagicLog(args...);
//Log(logType, oss.str());
}
So I need to replace std::cout with the oss that I made in the LogAll function, I tried passing it as an argument to the other functions but it was complaining about a "deleted function"...
So: How can I get a recursive variadic function to accept another parameter, the ostringstream?
I don't really understand your problem. Just like what you did with your LogAll function, passing an ostream& as first parameter works like a charm:
#include <iostream>
#include <sstream>
template <typename T>
void MagicLog(std::ostream& o, T t)
{
o << t << std::endl;
}
template<typename T, typename... Args>
void MagicLog(std::ostream& o, T t, Args... args) // recursive variadic function
{
MagicLog(o, t);
MagicLog(o, args...);
}
template<typename... Args>
void LogAll(int logType, Args... args)
{
std::ostringstream oss;
MagicLog(oss, args...);
std::cout << oss.str();
}
int main()
{
LogAll(5, "HELLO", "WORLD", 42);
}
It was also possible to eliminate the duplicate code from your MagicLog function.