Different behavior needed if template template parameter is vector - c++

We are using one internal c++ library that takes std::vector as input however i want to write wrapper function that should be able to accept std::vector, std::set or std::unordered_set but when input passed to this wrapper function is std::vector itself i don't want to copy that in temporary vector so is there any way to avoid this unnecessary copy.
Sample Reference code will explain this issue with more clarity :
#include <iostream>
#include <set>
#include <vector>
#include <unordered_set>
void print(const std::vector<int>& argVec)
{
for(auto elem:argVec)
{
std::cout<<elem<<std::endl;
}
}
template<template<typename...>class VecOrSet>
void wrapper(const VecOrSet<int>& input)
{
//How to avoid this temporary vector if input argument is vector itself
std::vector<int> temp(input.begin(),input.end());
print(temp);
}
int main()
{
std::vector<int> v{1,2,3};
std::set<int> s{4,5,6};
std::unordered_set<int> us{7,8,9};
wrapper(v);
wrapper(s);
wrapper(us);
return 0;
}

You can add a full specialization.
template<>
void wrapper(const std::vector<int>& input)
{
print(input);
}
Or just add another overload.
void wrapper(const std::vector<int>& input)
{
print(input);
}
Or use constexpr if (since C++17).
template<template<typename...>class VecOrSet>
void wrapper(const VecOrSet<int>& input)
{
if constexpr (std::is_same_v<VecOrSet<int>, std::vector<int>>) {
print(input);
} else {
std::vector<int> temp(input.begin(),input.end());
print(temp);
}
}

Another solution could be initialize temp passing through another function: getIntVect(), with a generic version
template <typename VoS>
std::vector<int> getIntVect (VoS const & input)
{ return { input.cbegin(), input.cend() }; }
that copy the input in a std::vector<int>, and a specific version for std::version<int>
std::vector<int> const & getIntVect (std::vector<int> const & input)
{ return input; }
that return a const-reference to the input (that is: avoid the copy)
So, using decltype(auto),
template<template<typename...>class VecOrSet>
void wrapper(const VecOrSet<int>& input)
{
decltype(auto) temp { getIntVect(input) };
print( temp );
}
temp is a reference to input, when VecOrSet is std::vector, or a copy of input, otherwise.
-- EDIT --
The OP is dubious
I think this line decltype(auto) temp{getIntVect(input)}; will call copy constructor of vector if input is vector
This is true for auto temp{getIntVect(input)};, as pointed by T.C. (thanks!). Not for decltype(auto) temp{getIntVect(input)};
Try compiling and running the following code
#include <iostream>
struct A
{
A ()
{ std::cout << "- default constructor" << std::endl; }
A (A const &)
{ std::cout << "- copy constructor" << std::endl; }
A (A &&)
{ std::cout << "- move constructor" << std::endl; }
};
template <typename T>
A foo (T const &)
{ return {}; }
A const & foo (A const & a)
{ return a; }
int main ()
{
std::cout << "--- 000" << std::endl;
A a0;
std::cout << "--- 001" << std::endl;
auto a1 { foo(a0) };
std::cout << "--- 002" << std::endl;
decltype(auto) a2 { foo(a0) };
std::cout << "--- 003" << std::endl;
decltype(auto) a3 { foo(0) };
std::cout << "--- 004" << std::endl;
}
I get (from g++ and clang++) this output
--- 000
- default constructor
--- 001
- copy constructor
--- 002
--- 003
- default constructor
--- 004
As you can see, auto a1 { foo(a0) }; call the copy constructor of A (because auto become A and A a1 { foo(a0) }; cause the the copy of the value returned by foo()) but decltype(auto) a2 { foo(a0) }; doesn't call contructors (because decltype(auto) become A const & and A const & a2 { foo(a0) }; simply link a2 to foo(a0) (so to a0).

Related

Why doesn't copy elision work in my static functional implementation?

I am trying to implement a "static" sized function, that uses preallocated store, unlike std::function that uses dynamic heap allocations.
#include <utility>
#include <cstddef>
#include <type_traits>
template <typename T, size_t StackSize = 64>
class static_function;
// TODO: move and swap
// - can move smaller instance to larger instance
// - only instances of the same size are swappable
// TODO: condiotnal dynamic storage?
template <typename Ret, typename ... Args, size_t StackSize>
class static_function<Ret(Args...), StackSize>
{
public:
constexpr static size_t static_size = StackSize;
using return_type = Ret;
template <typename Callable>
constexpr explicit static_function(Callable &&callable)
: pVTable_(std::addressof(v_table::template get<Callable>()))
{
static_assert(sizeof(std::decay_t<Callable>) <= static_size, "Callable type is too big!");
new (&data_) std::decay_t<Callable>(std::forward<Callable>(callable));
}
constexpr return_type operator()(Args ... args) const
{
return (*pVTable_)(data_, std::move(args)...);
}
~static_function() noexcept
{
pVTable_->destroy(data_);
}
private:
using stack_data = std::aligned_storage_t<static_size>;
struct v_table
{
virtual return_type operator()(const stack_data&, Args &&...) const = 0;
virtual void destroy(const stack_data&) const = 0;
template <typename Callable>
static const v_table& get()
{
struct : v_table {
return_type operator()(const stack_data &data, Args &&... args) const override
{
return (*reinterpret_cast<const Callable*>(&data))(std::move(args)...);
}
void destroy(const stack_data &data) const override
{
reinterpret_cast<const Callable*>(&data)->~Callable();
}
} constexpr static vTable_{};
return vTable_;
}
};
private:
stack_data data_;
const v_table *pVTable_;
};
However, it does not work as expected, since copies of the callable (copy elision does not kick in like in just lambda).
Here is what is expected vs actiual behavior with -O3:
#include <iostream>
#include <string>
int main()
{
struct prisoner
{
std::string name;
~prisoner()
{
if (!name.empty())
std::cout << name << " has been executed\n";
}
};
std::cout << "Expected:\n";
{
const auto &func = [captured = prisoner{"Pvt Ryan"}](int a, int b) -> std::string {
std::cout << captured.name << " has been captured!\n";
return std::string() + "oceanic " + std::to_string(a + b);
};
std::cout << func(4, 811) << '\n';
}
std::cout << "THE END\n\n";
std::cout << "Actual:\n";
{
const auto &func = static_function<std::string(int, int)>([captured = prisoner{"Pvt Ryan"}](int a, int b) -> std::string {
std::cout << captured.name << " has been captured!\n";
return std::string() + "oceanic " + std::to_string(a + b);
});
std::cout << func(4, 811) << '\n';
}
std::cout << "THE END!\n";
return 0;
}
Output:
Expected:
Pvt Ryan has been captured!
oceanic 815
Pvt Ryan has been executed
THE END
Actual:
Pvt Ryan has been executed
Pvt Ryan has been captured!
oceanic 815
Pvt Ryan has been executed
THE END!
https://godbolt.org/z/zc3d1Eave
What did I do wrong within the implementation?
It is not possible to elide the copy/move. The capture is constructed when the lambda expression is evaluated in the caller resulting in a temporary object. That lambda is then passed to the constructor and the constructor explicitly constructs a new object of that type in the storage by copy/move from the passed lambda. You can't identify the object created by placement-new with the temporary object passed to the constructor.
The only way to resolve such an issue is by not constructing the lambda with the capture that should not be copied/moved in the caller at all, but to instead pass a generator for the lambda which is then evaluated by the constructor when constructing the new object with placement-new. Something like:
template <typename CallableGenerator, typename... Args>
constexpr explicit static_function(CallableGenerator&& generator, Args&&... args)
: pVTable_(std::addressof(v_table::template get<std::invoke_result_t<CallableGenerator, Args...>>()))
{
static_assert(sizeof(std::decay_t<std::invoke_result_t<CallableGenerator, Args...>>) <= static_size, "Callable type is too big!");
new (&data_) auto(std::invoke(std::forward<CallableGenerator>(generator), std::forward<Args>(args)...);
}
//...
const auto &func = static_function<std::string(int, int)>([](auto str){
return [captured = prisoner{str}](int a, int b) -> std::string {
std::cout << captured.name << " has been captured!\n";
return std::string() + "oceanic " + std::to_string(a + b);
});
}, "Pvt Ryan");
// or
const auto &func = static_function<std::string(int, int)>([str="Pvt Ryan"]{
return [captured = prisoner{str}](int a, int b) -> std::string {
std::cout << captured.name << " has been captured!\n";
return std::string() + "oceanic " + std::to_string(a + b);
});
});
This requires C++17 or later to guarantee that the copy/move is elided. Before C++17 the elision cannot be guaranteed in this way with a lambda. Instead a manually-defined function object must be used so that its constructor can be passed the arguments like I am doing here with the generator. That would be the equivalent of emplace-type functions of standard library types.

Modifying unnamed objects via r-value semantics

I have a method that modifies objects passed by reference:
class MyClass;
MyClass& modify (MyClass& x) { ...; return x; }
What's the right way to extend modify to unnamed objects avoiding extra copies, so that the following code is valid?
MyClass createMyClass () { ... }
MyClass x = modify(createMyClass());
// instead of:
MyClass x = createMyClass();
modify(x);
PS: MyClass implements efficient moving.
It seems this code works with perfect forwarding, but not sure if you like it or not:
#include <memory>
#include <iostream>
struct T {
T() {std::cout << this << " ctor\n";}
~T() {std::cout << this << " dtor\n";}
T(const T& t) {std::cout << this << " cc\n"; i = t.i;}
T(T&& t) {std::cout << this << " mc\n"; i = t.i;}
T& operator=(const T& t) {std::cout << this << " ca\n"; i = t.i; return *this;}
T& operator=(T&& t) {std::cout << this << " ma\n"; i = t.i; return *this;}
int i = 0;
};
//T& modify (T& t) {t.i++; return t; }
template<typename X>
X modify (X&& t) { t.i++; return t; }
T create_T () { return T{}; }
int main() {
T t1 = modify(create_T());
std::cout << t1.i << "\n----------\n";
// instead of:
T t2 = create_T();
modify(t2);
std::cout << t2.i << "\n";
return 0;
}
And output will be like this:
0x7fff616d0fe0 ctor
0x7fff616d0fe8 mc
0x7fff616d0fe0 dtor
1
----------
0x7fff616d0fd8 ctor
1
0x7fff616d0fd8 dtor
0x7fff616d0fe8 dtor
You can also replace prefect forwarding with these 2 lines:
T& modify (T& t) { t.i++; return t; }
T modify (T&& t) { t.i++; return t; }
but not very interesting method.
Update:
For the reason that I don't know, following code does not generate similar output like perfect forwarding:
auto modify (auto&& t) { t.i++; return t; }
and output will be like this:
0x7ffcfa65c9a8 ctor
0x7ffcfa65c9b0 mc
0x7ffcfa65c9a8 dtor
1
----------
0x7ffcfa65c9a8 ctor
0x7ffcfa65c9b8 cc
0x7ffcfa65c9b8 dtor
1
0x7ffcfa65c9a8 dtor
0x7ffcfa65c9b0 dtor
Maybe the more experienced ones can tell the reasoning behind this difference.

Template customization point with default behavior to do nothing

I have a generic code at which point I leave a possibility to modify data.
if (impl_.mode() == implementation_type::manual)
{
const auto steering = custom::customize_steering(impl_, input.steering());
impl_.output_interface()(steering);
return impl_.do_continue(impl_.params().period());
}
By default I want customize_steering to be optimized out.
Now I have it this way:
template<typename ImplT, typename SteeringT>
constexpr std::decay_t<SteeringT> customize_steering(ImplT &, SteeringT &&steering)
{
return std::forward<SteeringT>(steering);
}
Is it the right way to do or is it too convoluted and passing by const reference will do fine?
I decided to check, and I don't get the results I expect.
#include <iostream>
class foo
{
public:
foo() { std::cout << "Constructed" << std::endl; }
foo(const foo &) { std::cout << "Copy constructed" << std::endl; }
foo(foo &&) { std::cout << "Move constructed" << std::endl; }
~foo() { std::cout << "Destroyed" << std::endl; }
};
template<typename T>
std::decay_t<T> do_nothing(T &&t)
{
return std::forward<T>(t);
// return t;
}
int main(int, char **)
{
const auto &fcr = do_nothing(do_nothing(do_nothing(foo())));
const auto fc = do_nothing(fcr);
return 0;
}
The output is (MinGW64):
Constructed
Move constructed
Move constructed
Move constructed
Destroyed
Destroyed
Destroyed
Copy constructed
Destroyed
Destroyed
What I expected:
Without mandatory copy elision: Constructor -> Move constructor.
With mandatory copy elision (C++17): Constructor (for const auto fc).
How can I make it work?

Is storing a reference to a (possibly) temporary object legal as long as the reference doesn't outlive the object?

After reading about std::variant and the handy overloaded trick I set myself a challenge (an exercise) to write a helper that would allow me to write
visit(variant).with([](int){}, [](char){});
instead of
std::visit(overloaded{[](int){}, [](char){}}, variant);
I'd also like it to work in other cases, like
VariantT variant;
VisitorT visitor;
visit(variant).with(visitor);
visit(VariantT{std::in_place_type<int>, 1}).with(visitor);
const VariantT constVariant;
visit(constVariant).with([](auto&&){});
You get the idea.
Here's what I came up with (note that I've changed the name from overloaded to Visitor:
#include <iostream>
#include <string>
#include <utility>
#include <variant>
template <typename... T>
struct Visitor : public T...
{
using T::operator()...;
};
template <typename... T>
Visitor(T...)->Visitor<T...>;
template <typename VariantT>
struct Helper
{
Helper(VariantT& variant)
: m_variant{variant}
{}
Helper(const Helper&) = delete;
Helper(Helper&&) = delete;
Helper& operator=(const Helper&) = delete;
Helper& operator=(Helper&&) = delete;
~Helper() = default;
template <typename VisitorT, typename... VisitorsT>
decltype(auto) with(VisitorT&& visitor,
VisitorsT&&... visitors) && // this function is ref-qualified so we can only call this on a temp object and we can
// be sure that the variant lives at least as long as us
{
if constexpr (sizeof...(visitors) == 0)
{
return std::visit(std::forward<VisitorT>(visitor), m_variant);
}
else
{
return std::visit(Visitor{visitor, visitors...}, m_variant);
}
}
private:
VariantT& m_variant;
};
template <typename VariantT>
decltype(auto) visit(VariantT&& variant)
{
// no forwarding here, even if an rvalue was passed, pass an lvalue ref
return Helper{variant};
}
int main()
{
visit(std::variant<int>{std::in_place_type<int>, -7})
.with([](int i) { std::cout << "got an int: " << i << std::endl; },
[](std::string str) { std::cout << "got a string: " << str << std::endl; });
std::variant<int, std::string> v = "String";
visit(v).with([](int i) { std::cout << "got an int: " << i << std::endl; },
[](std::string str) { std::cout << "got a string: " << str << std::endl; });
visit(v).with([](int& i) { i += 7; },
[](std::string& str) { str += "modified"; });
std::cout << visit(v).with([](int i) { return std::to_string(i); },
[](std::string str) { return str; }) << std::endl;
}
The question is: is storing references in the Helper like that perfectly legal?
My understanding is that temporary objects live until the end of the expression, so I guess this is ok?
Will it do The Right Thing? Are there any pitfalls in that code?
I've tested this code on both msvc and gcc and I'm not seeing anything wrong, but it doesn't mean this works fine in all cases.
The only obvious problem is that you can have things like
decltype(auto) silly() {
std::variant<int, std::string> v = "String";
return visit(v);
}
int main()
{
silly().with([](auto){ std::cout << "dangling" << std::endl; };
}
But such things are relatively easy to spot in review.

Correctly measuring constituent fundamental operations in sorting

I am trying to add some telemetry to the sorting functions in the standard library. I want to count the number of swaps, moves (including the ones invoked by the swap), and comparisons operations in the std::sort() and std::stable_sort() functions.
In order to do so, I am taking the following steps:
Create a struct to encapsulate the counts of the operations I want to measure
Create a wrapper for the comparison function object to increment the comparison count
Specialize the std::swap() function and increment the swap count
Create a custom move function and invoke that from the specialized std::swap() function created in step #3
Finally, run std::sort() and std::stable_sort() and gather stats
I get the following output, which may be correct because the std::stable_sort() is not likely to invoke any swaps:
std::sort(): Moves: 68691, Swaps: 22897, Comparisons: 156959
std::stable_sort(): Moves: 0, Swaps: 0, Comparisons: 183710
However, if I replace #4 step above with this:
Specialize the std::move() function and increment the move count
The output has 0 moves for std::sort(), which is definitely not right:
std::sort(): Moves: 0, Swaps: 22897, Comparisons: 156959
std::stable_sort(): Moves: 0, Swaps: 0, Comparisons: 183710
Furthermore, my compiler's (LLVM 8.1.0 on Darwin 16.5.0) implementation of std::stable_sort() is ridden with move() calls. So, I am also expecting non-zero move count in the output.
Please take a look at my code below (MVCE for the first case followed by the excerpt for the second/broken case) and see if you can answer these questions:
Is my overall approach for stats collection reasonable?
Why is the second case broken and how may I fix it?
Is there a way to simplify (make less verbose) the wrapper for the comparison functor, at least the std::ref() part?
Base code (for the first case):
#include<iostream>
#include<sstream>
#include<vector>
#include<cassert>
#include<numeric>
#include<algorithm>
namespace test {
// struct to store the count of swaps, moves, and comparisons
struct sort_stats {
long long int __count_swaps;
long long int __count_moves;
long long int __count_comps;
void reset_stats() { __count_swaps = 0; __count_moves = 0; __count_comps = 0; }
void incr_count_swaps() { ++__count_swaps; }
void incr_count_moves() { ++__count_moves; }
void incr_count_comps() { ++__count_comps; }
std::string to_str() {
std::stringstream ss;
ss << "Moves: " << __count_moves
<< ", Swaps: " << __count_swaps
<< ", Comparisons: " << __count_comps;
return ss.str();
}
};
// static instance of stats
static sort_stats __sort_stats;
// my custom move template
template<class T>
inline
typename std::remove_reference<T>::type&&
move(T&& x) noexcept
{
test::__sort_stats.incr_count_moves();
return static_cast<typename std::remove_reference<T>::type&&>(x);
}
// Wrapper for comparison functor
template <class _Comp>
class _WrapperComp
{
public:
typedef typename _Comp::result_type result_type;
typedef typename _Comp::first_argument_type arg_type;
_WrapperComp(_Comp comp) : __comp(comp) { }
result_type operator()(const arg_type& __x, const arg_type& __y)
{
test::__sort_stats.incr_count_comps();
return __comp(__x, __y);
}
private:
_Comp __comp;
};
}
// Specialization of std::swap (for int type)
namespace std {
template<>
inline
void
swap<int>(int& a, int&b) noexcept(is_nothrow_move_constructible<int>::value
&& is_nothrow_move_assignable<int>::value)
{
test::__sort_stats.incr_count_swaps();
using test::move;
int temp(move(a));
a = move(b);
b = move(temp);
}
}
using namespace test;
int main()
{
const size_t SIZE = 10000;
auto v = std::vector<int>(SIZE);
std::iota(v.begin(), v.end(), 0);
auto wrapper_less = _WrapperComp<std::less<int>>(std::less<int>());
auto ref_wrapper_less = std::ref(wrapper_less);
// Run std::sort() and gather stats
std::random_shuffle(v.begin(), v.end());
std::cout << "std::sort(): ";
__sort_stats.reset_stats();
std::sort(v.begin(), v.end(), ref_wrapper_less);
assert(std::is_sorted(v.begin(), v.end()));
std::cout << __sort_stats.to_str() << "\n";
// Run std::stable_sort() and gather stats
std::random_shuffle(v.begin(), v.end());
std::cout << "std::stable_sort(): ";
__sort_stats.reset_stats();
std::stable_sort(v.begin(), v.end(), ref_wrapper_less);
assert(std::is_sorted(v.begin(), v.end()));
std::cout << __sort_stats.to_str() << "\n";
return 0;
}
Here is the diff excerpt for the second case:
namespace std {
// Add specialization for std::move()
template<>
inline
std::remove_reference<int>::type&&
move<int>(int&& x) noexcept
{
test::__sort_stats.incr_count_moves();
return static_cast<std::remove_reference<int>::type&&>(x);
}
// Invoke std::move() instead of test::move from the specialized std::swap() function
template<>
inline
void
swap<int>(int& a, int&b) noexcept(is_nothrow_move_constructible<int>::value
&& is_nothrow_move_assignable<int>::value)
{
test::__sort_stats.incr_count_swaps();
using std::move;
int temp(move(a));
a = move(b);
b = move(temp);
}
}
I was able to solve this following Igor's suggestion about using a custom type (instead of int, which I was using earlier) and instrumenting the custom copy/move constructors/assignment operators. I also added an instrumented custom swap() function for the type, which simplified the std::swap() function template specialization.
Now, I get the following output:
std::sort(): Moves: 101606, Copies: 0, Swaps: 32821, Compares: 140451
std::stable_sort(): Moves: 129687, Copies: 0, Swaps: 0, Compares: 121474
If the move constructor/assignment-operators are not defined, the Moves counts above show up under Copies.
The following code works:
#include<iostream>
#include<vector>
#include<cassert>
#include<numeric>
#include<sstream>
#include<algorithm>
namespace test {
struct sort_stats {
long long int __count_swaps;
long long int __count_moves;
long long int __count_copies;
long long int __count_comps;
void reset_stats()
{ __count_swaps = __count_moves = __count_copies = __count_comps = 0; }
void incr_count_swaps() { ++__count_swaps; }
void incr_count_moves() { ++__count_moves; }
void incr_count_copies() { ++__count_copies; }
void incr_count_comps() { ++__count_comps; }
std::string to_str() {
std::stringstream ss;
ss << "Moves: " << __count_moves
<< ", Copies: " << __count_copies
<< ", Swaps: " << __count_swaps
<< ", Compares: " << __count_comps;
return ss.str();
}
};
static sort_stats __sort_stats;
struct my_type
{
int x;
// default constructor
my_type() = default;
// copy constructor
my_type(const my_type& other) : x (other.x)
{
__sort_stats.incr_count_copies();
}
// move constructor
my_type(my_type&& other) noexcept : x (std::move(other.x))
{
__sort_stats.incr_count_moves();
}
// copy assignment operator
my_type& operator=(const my_type& other)
{
__sort_stats.incr_count_copies();
x = other.x;
return *this;
}
// move assignment operator
my_type& operator=(my_type&& other)
{
__sort_stats.incr_count_moves();
x = std::move(other.x);
return *this;
}
// '<' operator
bool operator<(const my_type& other) const
{
__sort_stats.incr_count_comps();
return x < other.x;
}
// swap
void swap(my_type& other)
{
__sort_stats.incr_count_swaps();
my_type temp(std::move(*this));
*this = std::move(other);
other = std::move(temp);
}
// overload assignment operator for std::iota
my_type& operator=(const int& val)
{
x = val;
return *this;
}
};
} // namespace test
namespace std {
using test::my_type;
// std::swap specialized for test::my_type
template<>
inline
void
swap<my_type>(my_type& a, my_type& b) noexcept(is_nothrow_move_constructible<my_type>::value
&& is_nothrow_move_assignable<my_type>::value)
{
a.swap(b);
}
} // namespace std
using namespace test;
int main()
{
const size_t SIZE = 10000;
auto v = std::vector<my_type>(SIZE);
std::iota(v.begin(), v.end(), 0);
std::random_shuffle(v.begin(), v.end());
std::cout << "std::sort(): ";
__sort_stats.reset_stats();
std::sort(v.begin(), v.end());
std::cout << __sort_stats.to_str() << "\n";
assert(std::is_sorted(v.begin(), v.end()));
std::random_shuffle(v.begin(), v.end());
std::cout << "std::stable_sort(): ";
__sort_stats.reset_stats();
std::stable_sort(v.begin(), v.end());
std::cout << __sort_stats.to_str() << "\n";
assert(std::is_sorted(v.begin(), v.end()));
return 0;
}