In C++, I have a set of variadic template functions that I'd like to accept an arbitrary number of parameters, either constant references or as r-value references (so I can move things rather than copy when possible). However, I find that the version that accepts the constant reference is called even when I'm wrapping arguments in std::move().
// Accept end of parameters.
void example () {}
// Accept a non-constant R-value reference to do a move
template <typename... More>
void example (std::string &&value, More... parameters) {
std::cout << value << ": moved" << std::endl;
example (parameters...);
}
// Accept a constant reference parameter.
template <typename... More>
void example (const std::string &value, More... parameters) {
std::cout << value << ": copied" << std::endl;
example (parameters...);
}
int main (int, char **) {
std::string first { "first" };
std::string second { "second" };
std::string third { "third" };
std::cout << "Trying variadic with move as second parameter: " << std::endl;
example (first, std::move (second), third);
// std::cout << "Trying variadic with move as first parameter: " << std::endl;
// This next line won't even compile when uncommented
// example (std::move (first), std::move (second), third);
return 0;
}
The output is:
Trying variadic with move as second parameter:
first: copied
second: copied
third: copied
instead of the expected:
Trying variadic with move as second parameter:
first: copied
second: moved
third: copied
And as a bonus, when I wrap the first argument in std::move(), I get a compile error on both g++7 and clang 9.
What am I doing wrong?
There are several problems here:
More... parameters always receives arguments by value (as long as the template parameters are deduced), because types in typename ...More will never be deduced as references.
All arguments passed to example in example(parameters...); will always be lvalues.
The string && overload can't call the const string & one, because it's not yet declared at that point.
Instead of passing by value, you should use forwarding references and std::forward. And you need to declare the const string & overload before defining the string && one.
void example() {}
// Declare lvalue overload.
template <typename ...More>
void example(const std::string &value, More &&... parameters);
// Rvalue overload.
template <typename... More>
void example (std::string &&value, More &&... parameters) {
std::cout << value << ": moved" << std::endl;
example(std::forward<More>(parameters)...);
}
// Lvalue overload.
template <typename ...More>
void example(const std::string &value, More &&... parameters) {
std::cout << value << ": copied" << std::endl;
example(std::forward<More>(parameters)...);
}
This works:
void example2 () {}
template<typename First, typename... More>
void example2( First &&value, More&&... parameters ) {
if constexpr( std::is_rvalue_reference_v<decltype(value)> ) {
std::cout << value << ": moved" << std::endl;
}
else {
std::cout << value << ": copied" << std::endl;
}
example2( std::forward<More>(parameters)... );
}
int main (int, char **) {
std::string first { "first" };
std::string second { "second" };
std::string third { "third" };
std::cout << "copy, move, copy: " << std::endl;
example2( first, std::move(second), third );
// if we really moved second in the previous call
// then second will be an empty string here.
std::cout << "move, move, copy: " << std::endl;
example2(std::move(first), std::move(second), third);
return 0;
}
If you don't forward parameters any && will be passed as &. This is the main issue with your attempt.
You also don't need two functions to handle value, if you make it a && template param it will handle both cases: lvalue and rvalue, which we can detect using is_rvalue_reference<>.
HolyBlackCat already mentioned why your code behaves as it does so I'm not going to explain that again here. Here is another solution using fold expressions:
template <typename T>
void process(T const& lvalue)
{
std::cout << lvalue << ": copied\n";
}
// the enable_if is necessary because T&& is a forwarding reference, not a rvalue reference
template <typename T, typename = std::enable_if_t<!std::is_lvalue_reference_v<T>>>
void process(T&& rvalue)
{
std::cout << rvalue << ": moved\n";
}
template <typename... Args>
void example(Args&&... args)
{
(process(std::forward<Args>(args)), ...);
}
Example
Related
I want to make a simple logger which automatically runs a function and returns its value.
The class is defined as:
template <typename R, typename... Args>
class Logger3
{
Logger3(function<R(Args...)> func,
const string& name):
func{func},
name{name}
{}
R operator() (Args ...args)
{
cout << "Entering " << name << endl;
R result = func(args...);
cout << "Exiting " << name << endl;
return result;
}
function<R(Args...)> func;
string name;
};
I want to pass the following simple add function to the logger:
int add(int a, int b)
{
cout<<"Add two value"<<endl;
return a+b;
}
By calling it this way:
auto caller = Logger3<int(int,int)>(add,"test");
However, it generates the following errors:
error: function returning a function
133 | Logger3(function<R(Args...)> func,
| ^~~~~~~
decorator.h:138:7: error: function returning a function
138 | R operator() (Args ...args)
| ^~~~~~~~
decorator.h:145:26: error: function returning a function
145 | function<R(Args...)> func;
There are 3 issues in your code:
The Logger3 class template requires R to be the return value of the function (and Args it's arguments).
(R is not a function type as implied by your attempt to instantiate Logger3).
Therefore instantiating the Logger3 in your case of a function that gets 2 ints and returns an int should be:
auto caller = Logger3<int, int, int>(add, "test");
Your Logger3 constructor should be public in order to invoke it from outside the class.
For efficiency reasons, you should use std::forward to forward the arguments from operator() to your function. This will avoid copy of the arguments (more significant in cases where their types are more complex than ints).
Note that in order for std::forward to work as expected, operator() has to be itself a variadic template using forwarding references (see below).
Complete fixed version:
#include <string> // std::string
#include <functional> // std::function
#include <utility> // std::forward, std::declval
#include <iostream> // std::cout
template <typename R, typename... Args>
class Logger3
{
public:
Logger3(std::function<R(Args...)> func,
const std::string& name) :
func{ func },
name{ name }
{}
// Template with forwarding references to avoid copies
// 'typename' arg is for SFINAE, and only enables if a
// function accepting 'Args...' can evaluate with 'UArgs...'
template <typename...UArgs,
typename = decltype(std::declval<R(*)(Args...)>()(std::declval<UArgs>()...))>
R operator() (UArgs&&...args)
{
std::cout << "Entering " << name << std::endl;
R result = func(std::forward<UArgs>(args)...);
std::cout << "Exiting " << name << std::endl;
return result;
}
private:
std::function<R(Args...)> func;
std::string name;
};
int add(int a, int b)
{
std::cout << "Add two value" << std::endl;
return a + b;
}
int main()
{
auto caller = Logger3<int, int, int>(add, "test");
auto res = caller(3, 4);
std::cout << "result: " << res << std::endl;
return 0;
}
Output:
Entering test
Add two value
Exiting test
result: 7
Demo: Godbolt.
A side note: better to avoid using namespace std - see here: Why is "using namespace std;" considered bad practice?.
You need to use template class partial specialization to get the type of R.
template <typename F>
class Logger3;
template <typename R, typename... Args>
class Logger3<R(Args...)>
{
// implementation details
};
which makes int match the template parameter R of the partial specialization when you explicitly specify Logger3<int(int,int)>.
I have a generic function that takes two arguments, compares them, and prints a message if they're not equal. Right now, I just have this relatively dumb function:
template <typename T>
static void AreEqual(const T& expected,
const T& actual,
const std::string& message = "") {
if (!(actual == expected)) {
std::cout << message;
}
}
This has worked adequately for many years. It's most often called with primitives, but also gets used to compare larger user-defined structs/classes.
I would like to extend the function by providing an overload that prints the expected and actual values when they don't match, but without breaking the function for classes that define operator== but don't define operator<<. My idea is to create an overload that uses SFINAE to disable the overload if operator<< is missing. I've come up with this so far:
template <
typename T,
typename = typename std::enable_if_t<
std::is_same_v<decltype(std::cout << *((T*)nullptr)), decltype(std::cout)>>>
static void AreEqual(const T& expected,
const T& actual,
const std::string& message = "") {
if (!(actual == expected)) {
std::cout << "Expected " << expected << ", got " << actual << ". " << message;
}
}
This compiles, but it doesn't get selected for T of int or std::string and I'm not sure why. My first suspicion is that my arguments to is_same_v are malformed somehow, but I have no idea how or how to figure out how to fix it if that's the case.
Question 1: Is all of this even necessary? Could I achieve the same result without template meta programming (preferably while sticking with C++11)
Question 2: If this is the best way forward, how to I effectively debug my templates?
You might do something like:
struct overload_low_priority {};
struct overload_high_priority : overload_low_priority {};
template <typename T>
static auto AreEqualImpl(const T& expected,
const T& actual,
const std::string& message,
overload_high_priority)
-> decltype(std::cout << expected, void()) // SFINAE
{
if (!(actual == expected)) {
std::cout << "Expected " << expected << ", got " << actual << ". " << message;
}
}
template <typename T>
static void AreEqualImpl(const T& expected,
const T& actual,
const std::string& message,
overload_low_priority) // Fallback
{
if (!(actual == expected)) {
std::cout << message;
}
}
template <typename T>
static void AreEqual(const T& expected,
const T& actual,
const std::string& message = "")
{
AreEqualImpl(expected, actual, message, overload_high_priority{});
}
I propose something different.
Instead of std::cout (or not) the values expected and actual, you could ever print the value returned by a function called from the values. So you could differentiate (overloading) the behavior of the called function.
I mean... suppose you write two version of the function maybePrint().
The first one is a template pass-through function, SFINAE enabled only if the template type is printable
template <typename T>
auto maybePrint (T const & t) -> decltype( std::cout << t, t )
{ return t; }
The second one, called when the first isn't available (so when the argument is unprintable), return an informative string (well... maybe choose a better string) [EDIT: modified following a report from Jarod42]
template <typename ... Ts>
std::string maybePrint (Ts const & ...)
{ return "[maybe not]"; }
So your AreEqual() become
template <typename T>
static void AreEqual(const T& expected,
const T& actual,
const std::string& message = "")
{
if ( ! (actual == expected) )
std::cout << "Expected " << maybePrint(expected) << ", got "
<< maybePrint(actual) << ". " << message;
}
I suggest this solution also because, tomorrow or in a far distant future, you could be tempted to modify the AreEqual() to differentiate the template types.
This because the following call
AreEqual(1, 2l, "abc\n");
gives a compilation error because the compiler can't choose between T = int (1 is int) and T = long (2l is long).
If you rewrite AreEqual() receiving two arguments of (potentially) two different types
template <typename T1, typename T2>
static void AreEqual (T1 const & expected,
T2 const & actual,
std::string const & message = "")
{
if ( ! (actual == expected) )
std::cout << "Expected " << maybePrint(expected) << ", got "
<< maybePrint(actual) << ". " << message;
}
the preceding call compile because T1 is deduced int and T2 is deduced long.
If you enable one version or the other of AreEqual() according T1 and T2 you have (potentially) four cases (T1 and T2 printable; T1 printable, T2 not; T2 printable, T1 not; both T1 and T2 not printable) so four version of AreEqual().
Working through maybePrint() you maintain a single AreEqual().
The following code does not compile:
#include <iostream>
#include <utility>
struct Foo
{
Foo() { std::cout << "Foo()" << std::endl; }
Foo(int) { std::cout << "Foo(int)" << std::endl; }
};
template <typename T>
struct Bar
{
Foo foo;
Bar(const Bar&) { std::cout << "Bar(const Bar&)" << std::endl; }
template <typename... Args>
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
{
std::cout << "Bar(Args&&... args)" << std::endl;
}
};
int main()
{
Bar<Foo> bar1{};
Bar<Foo> bar2{bar1};
}
Compiler error suggest to me that compiler was trying to use variadic template constructor instead of copy constructor:
prog.cpp: In instantiation of 'Bar<T>::Bar(Args&& ...) [with Args = {Bar<Foo>&}; T = Foo]':
prog.cpp:27:20: required from here
prog.cpp:18:55: error: no matching function for call to 'Foo::Foo(Bar<Foo>&)'
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
Why compiler does that and how to fix it?
This call:
Bar<Foo> bar2{bar1};
has two candidates in its overload set:
Bar(const Bar&);
Bar(Bar&); // Args... = {Bar&}
One of the ways to determine if one conversion sequence is better than the other is, from [over.ics.rank]:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence
S2 if
— [...]
— S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same
type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers
is more cv-qualified than the type to which the reference initialized by S1 refers. [ Example:
int f(const int &);
int f(int &);
int g(const int &);
int g(int);
int i;
int j = f(i); // calls f(int &)
int k = g(i); // ambiguous
—end example ]
The forwarding reference variadic constructor is a better match because its reference binding (Bar&) is less cv-qualified than the copy constructor's reference binding (const Bar&).
As far as solutions, you could simply exclude from the candidate set anytime Args... is something that you should call the copy or move constructor with SFINAE:
template <typename... > struct typelist;
template <typename... Args,
typename = std::enable_if_t<
!std::is_same<typelist<Bar>,
typelist<std::decay_t<Args>...>>::value
>>
Bar(Args&&... args)
If Args... is one of Bar, Bar&, Bar&&, const Bar&, then typelist<decay_t<Args>...> will be typelist<Bar> - and that's a case we want to exclude. Any other set of Args... will be allowed just fine.
While I agree that it's counter-intuitive, the reason is that your copy constructor takes a const Bar& but bar1 is not const.
http://coliru.stacked-crooked.com/a/2622b4871d6407da
Since the universal reference can bind anything it is chosen over the more restrictive constructor with the const requirement.
Another way to avoid the variadic constructor being selected is to supply all forms of the Bar constructor.
It's a little more work, but avoids the complexity of enable_if, if that's important to you:
#include <iostream>
#include <utility>
struct Foo
{
Foo() { std::cout << "Foo()" << std::endl; }
Foo(int) { std::cout << "Foo(int)" << std::endl; }
};
template <typename T>
struct Bar
{
Foo foo;
Bar(const Bar&) { std::cout << "Bar(const Bar&)" << std::endl; }
Bar(Bar&) { std::cout << "Bar(Bar&)" << std::endl; }
Bar(Bar&&) { std::cout << "Bar(Bar&&)" << std::endl; }
template <typename... Args>
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
{
std::cout << "Bar(Args&&... args)" << std::endl;
}
};
int main()
{
Bar<Foo> bar1{};
Bar<Foo> bar2{bar1};
}
The "std-way" to solve this issue is to put a parameter of std::in_place_t first. That way you have a clear type to force the compiler to use the templated constructor when you want and to not let it match when you don't want. You could check the way it is done here https://en.cppreference.com/w/cpp/utility/optional/optional.
I've got no idea why compiler gives me warnings about template instantiations.
Thats a piece of code which runs just fine and outputs lvalue/rvalue properly:
//template<typename T>
void overloaded(const /*T*/std::string& in)
{
std::cout << "lvalue" << std::endl;
}
//template<typename T>
void overloaded(/*T*/std::string&& in)
{
std::cout << "rvalue" << std::endl;
}
template<typename T>
void pass(T&& in)
{
overloaded(std::forward<T>(in));
}
int main()
{
std::string a;
pass(a);
pass(std::move(a));
getchar();
}
But i need to use it with templated type. So modifying the "overloaded" functions to
template<typename T>
void overloaded(const T& in)
{
std::cout << "lvalue" << std::endl;
}
template<typename T>
void overloaded(T&& in)
{
std::cout << "rvalue" << std::endl;
}
Gives template instantiations warnings, (when to me its clear T should be std::string), and console outputs rvalue 2 times instead of lvalue first.
What am i doing wrong?
Templates such as T&& are special. They are called "forwarding references". They have special deduction rules for functions like:
template<typename T>
void overloaded(T&& in)
Assume for a moment that overloaded is not overloaded. If you pass an lvalue expression of type std::string to overloaded, T will deduce as std::string&. If you pass an rvalue expression of type std::string to overloaded, T will deduce to std::string. You can use this knowledge to do this:
template<typename T>
void overloaded(T&& in)
{
if (std::is_lvalue_reference<T>::value)
std::cout << "lvalue" << std::endl;
else
std::cout << "rvalue" << std::endl;
}
In general, it is an anti-pattern to overload T&& templates with anything else. These special templates come in handy when you want to catch everything.
I want to define a template function that gets one argument passed by value for all types but std::string (and const char*).
template<typename T>
void foo( T value )
{
// some code using value
}
The std::string version should behave exactly as the template version, but have its parameter passed by const&.
What is the best approach to do what I want without duplicating the body of foo()?
The best I was able to think is to wrap the code using value inside another function, and then call it inside all versions of foo() (the template version and the std::string overload). Is there another way? For example, is it possible to call the template version from within the std::string overload?
EDIT
What I want to know is a good rule of thumb for avoiding code duplication among various specializations and overloads. What is a good pattern to follow? Shall I define a wrapper function for the body and then call that from within all overloads/specializations, or there is another way?
In order to avoid code duplication, the answer by 101010 can be extended to actually call the template from within the overload:
#include <string>
#include <iostream>
#include <type_traits>
#include <boost/core/demangle.hpp>
template<typename T>
void foo( T value )
{
std::cout << "inside template" << std::endl;
std::cout << boost::core::demangle(typeid(value).name()) << std::endl;
}
void foo(const std::string &value)
{
std::cout << "inside const string overload" << std::endl;
foo<const std::string&>(value);
}
int main()
{
foo(10);
foo(std::string("hello"));
return 0;
}
output
inside template
int
inside const string overload
inside template
std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >
live example
Simple solution: provide an overload for std::string:
void foo( std::string const &value ) {
// some code using value
}
I think what you are looking for is rvalue signature in C++ 11.
Its as simple as:
#include <iostream>
#include <string>
template<typename T>
void foo(T&& value)
{
std::cout << "was passed by refernece:" << std::is_lvalue_reference<T&&>::value << std::endl;
std::cout << value << std::endl;
}
int main()
{
std::string text = "hello";
foo(text);
foo(1);
}
You can either pass the parameter by reference or by value and the rvalue rules will use the appropriate type.
You can define a type-trait-like class that will convert std::string to std::string& and will keep the type for all other types:
template<class T>
struct helper {
typedef T type;
};
template<>
struct helper<std::string> {
typedef std::string& type; // or const std::string& type if you want
};
template<typename T>
void foo( typename helper<T>::type value, T value2 )
{
value = value2;
}
int main()
{
int a = 10;
foo(a, 42);
std::cout << a << std::endl; // prints 10
std::string s = "abc";
foo(s, std::string("def"));
std::cout << s << std::endl; // prints def
}
Full example: http://coliru.stacked-crooked.com/a/96cf78e6c4846172
UPD: as noted by #PiotrSkotnicki, having only one parameter makes type-deduction fail. However, I will keep the answer as it might be helpful in case you indeed have several parameters of type T or if you are ok with specifying explicit template parameter to foo.
UPD2: To solve the type-deduction problem, you may add another wrapper:
template<typename T>
void foo_helper( typename helper<T>::type value )
{
value = T();
}
template<typename T>
void foo(T& value)
{
foo_helper<T>(value);
}
This still might have some problems, so whether this is applicable to your usecase, is up to you to decide.
use std::enable_if + std::is_convertibale:
template<typename T>
typename std::enable_if<!std::is_convertible<T,std::string>::value>::type foo( T value )
{
// some code using value
}