Symmetric operator+ in terms of operator+= in modern C++? - gcc

I was reading this note on the implementation of symmetric operators in Boost.Operator https://www.boost.org/doc/libs/1_69_0/libs/utility/operators.htm#symmetry and I suspect it is awfully outdated.
The discussion revolves on what is the best way to implement operator+ generically, if a consistent operator+= is available. The conclusion there is that it is (was),
T operator+( const T& lhs, const T& rhs ){
T nrv( lhs ); nrv += rhs; return nrv;
}
because at the time some compilers supported NRVO, as opposed to RVO.
Now, with NRVO being mandatory, and all sorts of optimization being performed, is this still the case?
For example other version that may make sense now for certain cases is:
T operator+(T lhs, const T& rhs ){
T ret(std::move(lhs)); ret += rhs; return ret;
}
or
T operator+(T lhs, const T& rhs ){
lhs += rhs; return lhs;
}
Given a class that has a constructor, a move constructor, and reasonable operator+=. For example:
#include<array>
#include<algorithm>
using element = double; // here double, but can be more complicated
using array = std::array<double, 9>; // here array, but can be more complicated
array& operator+=(array& a, array const& b){
std::transform(begin(a), end(a), begin(b), begin(a), [](auto&& x, auto&& y){return x + y;});
return a;
}
array& operator+=(array&& a, array const& b){
std::transform(begin(a), end(a), begin(b), begin(a), [](auto&& x, auto&& y){return x + std::move(y);});
return a;
}
What is the best way to implement a symmetric operator+? Here is a set of possible codes
/*1*/ array sum(array const& a, array const& b){array tmp(a); tmp+=b; return tmp;} // need operator+= and copy-constructor
/*2*/ array sum(array const& a, array const& b){return array(a)+=b;} // needs operator+= && and copy-constructor
/*3*/ array sum(array a, array const& b){return std::move(a)+=b;} // needs operator+= && and can use move-constructor
/*4*/ array sum(array a, array const& b){array tmp(std::move(a)); tmp+=b; return tmp;} // needs operator+= and can use move-constructor
I tried it in https://godbolt.org/z/2YPhcg and just by counting the number of assembly lines, which all other things being equal might tell what is the best implementation. I get these mixed results:
| code | gcc -O2 | clang -O2 |
|:-----------|------------:|:------------:|
| /*1*/ | 33 lines | 64 lines |
| /*2*/ | 39 lines | 59 lines |
| /*3*/ | 33 lines | 62 lines |
| /*4*/ | 33 lines | 64 lines |
While /*3*/ and /*4*/ can benefit from calls of the form sum(std::move(a), b) or even sum(sum(a, c), b).
So is T tmp(a); tmp+=b; return tmp; still the best way to implement operator+(T [const&], T const&)?
It looks like if there is a move constructor and a moving +=, there are other possibilities but only seem to produce simpler assembly in clang.

If the signature is:
T operator+(T const& a, T const& b )
(as you say in the bolded question text), then the body should be:
return T(a) += b;
where the result object is the only T constructed. The version T nrv( lhs ); nrv += rhs; return nrv; theoretically risks the compiler not merging nrv with the result object.
Note that the above signature does not allow moving out of any of the arguments. If it's desirable to move out of the lhs, then this seems optimal to me:
T operator+(T const& a, T const& b)
{
return T(a) += b;
}
T operator+(T&& a, T const& b)
{
return T(std::move(a)) += b;
}
In both cases the result object is guaranteed to be the only object constructed. In the "classic" version taking T a, then the case of an rvalue argument would incur an extra move.
If you want to move out of the right-hand side then two more overloads can be added :)
Note that I have not considered the case of returning T&& for reasons described here

Related

How to remove the nth element from a tuple?

I'm trying to write a function that creates a new std::tuple from an existing one, with skipping the element on a given index. In example:
I have a tuple t defined as below:
constexpr auto t = std::tuple(1, 2, 3, 4);
And I want to copy it to another tuple. However, I want to skip the nth element. Let's say that in this case, the nth element I want to skip is 3 (this would mean that I want to skip the element with the index 2). This would result in a new tuple defined as:
std::tuple(1, 2, 4);
This is the closest I got until now:
template<std::size_t N, typename T, std::size_t ... is>
constexpr auto fun(T&& tp, std::index_sequence<is...>&& i) noexcept {
return std::tuple((is != N ? std::get<is>(tp) : 0) ...);
}
template<std::size_t N, std::size_t... elems>
constexpr auto fun2() noexcept {
constexpr auto t = std::tuple(elems...);
return fun<N>(std::forward_as_tuple(elems...), std::make_index_sequence<sizeof...(elems)>());
}
However, instead of removing the nth element, I set it to 0.
Ideally, I would change the return argument in the function fun() to create a new tuple using multiple temporary tuples:
return std::tuple_cat((is != N ? std::tuple(std::get<is>(tp)) : std::tuple()) ...);
However, the issue with this is that the ternary operator has to have matching types on both sides.
Another approach I tried was based on recursion:
template<std::size_t N, std::size_t head, std::size_t... tail>
constexpr auto fun3() noexcept {
if constexpr(!sizeof...(tail))
return std::tuple(head);
if constexpr(sizeof...(tail) - 1 == N)
return std::tuple_cat(fun3<N, tail...>());
if constexpr(sizeof...(tail) - 1 != N)
return std::tuple_cat(std::tuple(head), fun3<N, tail...>());
}
However, that was even more unsuccessful. In this case, if N is equal to 0, the nth element (which is the first element here as well) will still be used in the new tuple. Also, this won't even compile, because there's an issue with the second statement:
if constexpr(sizeof...(tail) - 1 == N)
What am I missing here? How can I copy a tuple and skip one of its elements during the copy?
I'm using C++17, and I need the function to be evaluated during compile-time.
What about
return std::tuple_cat( foo<is, N>::func(std::get<is>(tp)) ...);
where foo is a struct with specialization as follows?
template <std::size_t, std::size_t>
struct foo
{
template <typename T>
static auto func (T const & t)
{ return std::make_tuple(t); }
}
template <std::size_t N>
struct foo<N, N>
{
template <typename T>
static std::tuple<> func (T const &)
{ return {}; }
}
(caution: code not tested).
This is almost your ternary operator idea but without the problem of matching the types in both sides: only the right type is instantiated.
Another solution would be to create two index sequences that refer to the before and after parts of the tuple.
template<std::size_t nth, std::size_t... Head, std::size_t... Tail, typename... Types>
constexpr auto remove_nth_element_impl(std::index_sequence<Head...>, std::index_sequence<Tail...>, std::tuple<Types...> const& tup) {
return std::tuple{
std::get<Head>(tup)...,
// We +1 to refer one element after the one removed
std::get<Tail + nth + 1>(tup)...
};
}
template<std::size_t nth, typename... Types>
constexpr auto remove_nth_element(std::tuple<Types...> const& tup) {
return remove_nth_element_impl<nth>(
std::make_index_sequence<nth>(), // We -1 to drop one element
std::make_index_sequence<sizeof...(Types) - nth - 1>(),
tup
);
}
Here's a test for this function:
int main() {
constexpr auto tup = std::tuple{1, 1.2, 'c'};
constexpr auto tup2 = remove_nth_element<0>(tup);
constexpr auto tup3 = remove_nth_element<2>(tup);
static_assert(std::is_same_v<decltype(tup2), const std::tuple<double, char>>);
static_assert(std::is_same_v<decltype(tup3), const std::tuple<int, double>>);
return 0;
}
Live example
This solution has the advantages of not constructing intermediary tuples and not using std::tuple_cat, which both can be hard on compile times.
Just a few minutes after posting the question, I found a workaround. It's not ideal, but hey:
template<std::size_t N, typename T, std::size_t ... is>
constexpr auto fun(T&& tp, std::index_sequence<is...>&& i) noexcept {
return std::tuple((is < N ? std::get<is>(tp) : std::get<is+1>(tp)) ...);
}
template<std::size_t N, std::size_t... elems>
constexpr auto fun2() noexcept {
constexpr auto t = std::tuple(elems...);
return fun<N>(std::forward_as_tuple(elems...), std::make_index_sequence<sizeof... (elems) - 1>());
}
This way, we copy all of the elements prior to the nth element, and when we reach the nth element, we increase every next index for 1. We won't go out of range, since we pass the index_sequence that has 1 element less than the tuple that is passed.
I hope that this answer helps someone.

Create function with unknown number of std::array [duplicate]

This question already has answers here:
Concatenating a sequence of std::arrays
(9 answers)
Closed 3 years ago.
I am trying to make a function that pass that test:
TEST(MyCat, CheckOn2Arrays){
std::array<float, 3> vec1{1.0f, 2.0f, 3.0f};
std::array<float, 3> vec2{4.0f, 5.0f, 6.0f};
std::array<float, 6> r = MyCat(vec1, vec2);
EXPECT_EQ(r[0], 1.0f);
EXPECT_EQ(r[1], 2.0f);
EXPECT_EQ(r[2], 3.0f);
EXPECT_EQ(r[3], 4.0f);
EXPECT_EQ(r[4], 5.0f);
EXPECT_EQ(r[5], 6.0f);
}
I have writen that function:
template<class T, size_t N>
auto MyCat((std::array<T, N>) ... arrays) ->
decltype(std::array<T, N*sizeof...(arrays)>) {
std::array<T, sizeof...(arrays)*N> retArray;
int i = 0;
for(std::array<T, N> array: arrays){
T* = array.begin();
while(!T){
retArray[i] = &T;
i++;
T++;
}
}
return retArray;
}
The function must take an arbitrary number of arguments and return an object of type std::array. All arguments are of type std::array (T and N are the same for all function arguments).
Im getting that errors and cant understand how to solve them..
../src/test/../myproject/MyCat.h:4:29: error: expected primary-expression before ‘)’ token
auto MyCat((std::array<T, N>) ... arrays) ->
^
../src/test/../myproject/MyCat.h:4:6: warning: variable templates only available with
std=c++14 or -std=gnu++14
auto MyCat((std::array<T, N>) ... arrays) ->
^
../src/test/../myproject/MyCat.h:4:43: error: expected ‘;’ before ‘->’ token
auto MyCat((std::array<T, N>) ... arrays) ->
^
../src/test/MyCat_test.cc: In member function ‘virtual void
MyCat_CheckOn2Arrays_Test::TestBody()’:
../src/test/MyCat_test.cc:10:35: error: missing template arguments before ‘(’ token
std::array<float, 6> r = MyCat(vec1, vec2);
Here is a C++17 way to do it, but I don't consider it pretty:
template<class T, std::size_t ... N>
auto MyCat(std::array<T, N> ... arrays)
{
constexpr std::size_t NSum = (N + ...);
std::array<T, NSum> retArray;
auto outIt = retArray.begin();
((outIt = std::copy(arrays.begin(), arrays.end(), outIt)), ...);
return retArray;
}
https://godbolt.org/z/ha9NSY
This function takes any number of std::arrays with the same type but potentially different sizes. (You could disallow different sizes if you wanted, left as exercise to the reader.)
We compute the total size (by summing all N in a fold expression) at compile-time (constexpr) and then create a return array of that size.
Next, the ugly part: We want to perform some operation for every variadic argument, so we use a fold expression on the comma operator. We want to copy the current variadic argument (arrays) to the correct section of retArray. Thankfully, std::copy returns an updated iterator for the output, so we can use that to keep track of where to copy to. Note that you need parenthesis around both the comma-operated expression (the iterator assignment) and around the whole fold expression.
It works, but again, not pretty. And it's not even constexpr (because std::copy is not constexpr)...

STL algorithm for splitting a vector into multiple smaller ones based on lambda

Let's say I have a vector containing a struct with a member describing its target vector.
struct Foo
{
int target;
static const int A = 0;
static const int B = 1;
static const int C = 2;
};
std::vector<Foo> elements;
std::vector<Foo> As;
std::vector<Foo> Bs;
std::vector<Foo> Cs;
std::vector<Foo> others;
Now I want to move each Foo in one of the four other vectors based on the value of Target.
For example
auto elements = std::vector<Foo>{ {Foo::A}, {Foo::A}, {Foo::B} };
Should result in two elements in As, one in Bs and none in Cs or others. Elements should be empty afterwards.
I could as well do it myself, but I wonder if there is an STL algorithm I could use to do its job.
Standard algorithms usually don't operate on multiple output destinations, so it's hard to come up with a suitable solution here when you want to abstract away the destination containers through output iterators. What might come closest is std::copy_if. This could look like
// Help predicate creation:
auto pred = [](int target){ return [target](const Foo& f){ return f.target == target; }; };
std::copy_if(elements.begin(), elements.end(), std::back_inserter(As), pred(Foo::A));
std::copy_if(elements.begin(), elements.end(), std::back_inserter(Bs), pred(Foo::B));
std::copy_if(elements.begin(), elements.end(), std::back_inserter(Cs), pred(Foo::C));
std::copy_if(elements.begin(), elements.end(), std::back_inserter(others),
[](const Foo& f){ return false; /* TODO */ });
elements.clear();
If copying is more expensive than move-construction, you should pass std::make_move_iterator(elements.begin()) and the same for elements.end() to the algorithm. The issue here is that this doesn't scale. std::copy_if linearly traverses the input range, and the above has to do this four times. One traversal can be obtained e.g. like the following.
auto doTheWork = [&As, &Bs, &Cs, &others](const Foo& foo) {
if (foo.target == Foo::A)
As.push_back(foo);
else if (foo.target == Foo::B)
Bs.push_back(foo);
else if (foo.target == Foo::C)
Cs.push_back(foo);
else
others.push_back(foo);
};
std::for_each(elements.begin(), elements.end(), doTheWork);
In this scenario, we have at least employed a standard algorithm, but shifted the logic into a rather ugly lambda. Note that the above lambda will always copy its arguments, it needs some adjustments to properly work with std::move_iterators.
Sometimes, a good old range based for loop is the most readable solution.

Why can a std::tuple not be assigned with an initializer list?

I wonder why this choice is made. It would allow to write many functions in a very clear and neat way.. for instance:
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = { b, a };
while (b > 0)
std::tie(a, b) = { b, a % b };
return a;
}
std::initializer_list is a homogeneous collection of items, while std::tuple is heterogeneous. The only case where it makes sense to define a std::tuple::operator= for std::initializer_list is when the tuple is homogeneous and has the same size as the initializer list, which is a rare occurrence.
(Additional information in this question.)
Solution/workaround: you can use std::make_tuple instead:
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = std::make_tuple(b, a);
while (b > 0)
std::tie(a, b) = std::make_tuple(b, a % b);
return a;
}
...or std::tuple's constructor in C++17 (thanks to Template argument deduction for class templates):
int greatestCommonDivisor(int a, int b)
{
if (b > a)
std::tie(a, b) = std::tuple{b, a};
while (b > 0)
std::tie(a, b) = std::tuple{b, a % b};
return a;
}
Why does
std::tie(a,b) = {b, a};
not compile?
{} on the right hand side of an assignment can only call a non-explicit constructor of an argument of operator=.
The operator= overloads available are:
tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );
template< class... UTypes >
tuple& operator=( const tuple<UTypes...>& other );
template< class... UTypes >
tuple& operator=( tuple<UTypes...>&& other );
template< class U1, class U2 >
tuple& operator=( const pair<U1,U2>& p );
template< class U1, class U2 >
tuple& operator=( pair<U1,U2>&& p );
The template operator overloads cannot deduce their types from {} (note: this may change in C++17), leaving:
tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );
where tuple is std::tuple<int&, int&> in this case.
The tuple constructor for tuple<Ts...> that perfect forwards element-wise construction is explicit (#3 on that list). {} will not call an explicit constructor.
The conditionally non-explicit constructor takes Ts const&...; it does not exist if the Ts are non-copyable, and int& is non-copyable.
So there are no viable types to construct from {int&, int&}, and overload resolution fails.
Why does the standard not fix this? Well, we can do it ourselves!
In order to fix this, we'd have to add a special (Ts...) non-explicit constructor to tuple that only exists if the Ts types are all references.
If we write a toy tuple:
struct toy {
std::tuple<int&, int&> data;
toy( int& a, int& b ):data(a,b) {} // note, non-explicit!
};
toy toy_tie( int& a, int& b ) { return {a,b}; }
and use it, you'll notice that
std::tie(a, b) = {b, a};
compiles and runs.
However,
std::tie(a, b) = { b, a % b };
does not, as a%b cannot be bound to int&.
We can then augment toy with:
template<class...>
toy& operator=( std::tuple<int, int> o ) {
data = o;
return *this;
}
(+ defaulted special member functions. template<class...> ensures it has lower priority than the special member functions, as it should).
This which lets assign-from {int,int}. We then run it and... get the wrong result. The gcd of 5,20 is 20. What went wrong?
toy_tie(a, b) = std::tie( b, a );
with both a and b bound to references is not safe code, and that is what
toy_tie(a, b) = { b, a };
does.
In short, doing this right is tricky. In this case, you need to take a copy of the right hand side before assigning to be safe. Knowing when to take a copy and when not to is also tricky.
Having this work implicitly looks error prone. So it is, in a sense, accidental that it doesn't work, but fixing it (while possible) looks like a bad idea.
live example.

flatten vectors in a map

In C++, I have a map < int, vector < double > > or map < int, vector < some_Struct > >, I need to concatenate the vectors in this map and return the result.
The first version of the function is below:
#include <algorithm>
#include <vector>
vector < double >
flattenRecords(const map < int, vector < double > > & selected, int num_kept_reps)
{
vector < double > records;
int cnt = 0;
for (auto it = selected.begin(); it != selected.end(); ++it) {
records.insert(records.end(),
make_move_iterator(it->second.begin()),
make_move_iterator(it->second.end()));
cnt += 1;
if (cnt >= num_kept_reps)
break;
}
return records;
}
I know this is not what I intended to do, because I would like to keep the data in the map, and thus should not use make_move_iterator.
The codes can compile using g++ (GCC) 4.4.7 with the -std=c++0x flag.
So here is the question, I declare the map to be const, what happens when I try to use something like std::move to the vector in the map?
My second version is to use:
copy(it->second.begin(), it->second.end(), back_inserter(records));
I guess this does what I intend to do.
I am quite new to C++. The STL gives me a feeling of coding in python so I would like to try it.
Instead of using make_move_iterator(Iterator), just use Iterator if you would not like to move the elements. Eg:
records.insert(records.end(), it->second.begin(), it->second.end());
Your second version, as you guess, does indeed what you try to achieve.
Regarding your question about std::move on a const map, the std::move won't do anything in such a case. Since std::move is unconditional cast to rvalue, it'll cast the element to a const reference to an rvalue. Because it's const it'll match the lvalue ctor (copy ctor in this case), and not the move (copy) ctor.
Eg:
const std::string s1 = "Test";
const std::string s2 = std::move(s1);
This will invoke the copy constructor of std::string, not the move constructor. Hence, it'll do a copy, not a move.
This will do a move:
std::string s1 = "Test";
const std::string s2 = std::move(s1);
The s2 parameter in both examples does not have to be const. It makes no difference regarding the copy/move.
There is a 'pythonic' alternative, which you may like if you come from Python, using lambda and a "mapped-reduce" function
std::vector<double> merged = std::accumulate(selected.begin(),
selected.end(),
std::vector<double>(),
[](const std::vector<double>& a, std::vector<double>& b)
{
std::vector<double> result(a);
std::copy(b.begin(), b.end(), std::back_inserter(result));
return result;
});
);
std::move does not actually move data. It casts a reference to a r-value reference, which, if non-const, is a "movable" data type that move constructors can use. But it will never remove a const type qualifier, so using std::move on a const reference will not cast it to a movable type.

Resources