So I am trying to build a "sanitizer" function to filter formatting arguments before they are forwarded to printf.
template<typename A>
_CR_INLINE decltype(auto) sanitize_forward(A&& arg) {
return std::forward<A>(arg);
}
template<>
_CR_INLINE decltype(auto) sanitize_forward<std::string>(std::string&& arg) {
return std::forward<const char*>(arg.c_str());
}
So every std::string is supposed to "decay" into a const char*, in order to be properly formatted.
template<typename...Args>
_CR_INLINE void printf_safe(const std::string& format, Args&&...args) {
std::printf(format.c_str(), sanitize_forward<Args>(args)...);
}
I want the arguments to be perfectly forwarded to printf, which is why I return std::forward. But I can't really wrap my head around how this should be implemented.
1) Is decltype(auto) correct? It should preserve the r-value referenceness of the return of std::forward, right?
2) How should I specialize my template: std::string&& or std::string?
3) Deduced forwarding references should be the same as the actual type, right?
You almost completely misunderstand forwarding references; which is to be expected, they are confusing.
To understand them, you have to understand reference collapsing rules, std::forward itself, template argument deduction rules when deducing T&&, and how they tie together, at least to some extent.
First, reference collapsing. Look at this chart:
If Then Then
X is X& is X&& is
T T& T&&
T& T& T&
T const& T const& T const&
T&& T& T&&
if X is int, then X&& is int&&. If X is int&, then X&& is int&.
Reread that. Again. One more time. & wins over && when both are applied.
Next, deduction. I will tell lies here, but they are simplifying lies.
If you pass Foo& to a template deducing X&&, X is Foo& and X&& is Foo&.
If you pass Foo&& to a template deducing X&&, X is Foo and X&& is Foo&&.
Next, std::forward is a conditional move . For a reference variable X&& x, std::forward<X>(x) is decltype(x)(x) -- it casts x to the type it was declared as. If x is an rvalue reference, it casts x to an rvalue reference. This is needed because the type of the expression x is X& not X&& even if x is an rvalue reference. An rvalue reference is a reference to an rvalue, but is not itself an rvalue.
Now to fix your code.
template<class T>
struct tag_t{constexpr tag_t(){}};
template<class T>
constexpr tag_t<std::decay_t<T>> tag{};
template<class T>
auto sanitizer(tag_t<T>){
return [](auto&& t)->decltype(auto){
return decltype(t)(t);
};
}
template<class A>
decltype(auto) sanitize(A&& arg) {
return sanitizer(tag<A>)(std::forward<A>(arg));
}
auto sanitizer(tag_t<std::string>) {
return [](std::string const& s){return s.c_str();};
}
template<class...Ts>
void printf_safe(const std::string& format, Ts&&...ts) {
std::printf(format.c_str(), sanitize(std::forward<Ts>(ts))...);
}
I follow SRP (single responsibility principle) -- I split forward from sanitize.
Then I split which sanitizing action (sanitizer) from actually doing it (sanitize).
This let me write the std::string santization code once, without the perfect forwarding winning "by accident".
As an aside, I'd replace decay with remove ref and remove cv if you want to treat array args as non-pointers.
You can extend sanitize functionality by writing sanitizer overloads in either the namespace of tag_t or in the namespace of the type we are sanitizing (bit not in std naturally).
So I am trying to build a "sanitizer" function to filter formatting arguments before they are forwarded to
printf.
Your premise seems wrong. printf is a C function which uses va_arg - it doesn't do or need any perfect forwarding.
http://en.cppreference.com/w/cpp/io/c/fprintf
It is also never going to "consume" its arguments, but merely read from them. It doesn't make sense to distinguish temporaries and non-temporaries - just take const Args&... in your printf wrapper.
1) Is
decltype(auto)correct? It should preserve the r-value referenceness of the return ofstd::forward, right?
std::forward will either return an lvalue reference or rvalue reference. decltype(auto) will indeed preserve that.
Your specialization of sanitize_forward for std::string doesn't look useful - the std::forward<const char*> will always return a const char* &&. I don't think it's different from:
template<>
_CR_INLINE const char* sanitize_forward<std::string>(std::string&& arg) {
return arg.c_str();
}
Additionally, returning .c_str() for an rvalue reference to a std::string sounds very dangerous and incorrect: you're taking a pointer to the internal buffer of a string that's about to expire. You probably want to take const std::string& here.
2) How should I specialize my template:
std::string&&orstd::string?
How is it going to be called? Are you explicitly supplying a template argument? Is the template argument going to always be a non-reference, or is it going to be both an lvalue reference and a non-reference?
Since you have sanitize_forward<Args>, you will probably attempt to invoke both...
sanitize_forward<std::string>
sanitize_forward<std::string&>
...maybe with cv-qualifiers. You might want to supply an additional explicit std::decay_t<Args> parameter that deals with the "specialization" business.
3) Deduced forwarding references should be the same as the actual type, right?
Not sure what you mean by this. Could you elaborate?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With