The following structure fails to compile under C++11 due to the fact that I have declared the move assignment operator as noexcept:
struct foo
{
std::vector<int> data;
foo& operator=(foo&&) noexcept = default;
};
The default move assignment operator generated by the compiler is noexcept(false) due to the fact that std::vector<int>'s move assignment is also noexcept(false). This in turn is due to the fact that the default allocator has std::allocator_traits<T>:: propagate_on_container_move_assignment set to std::false_type. See also this question.
I believe this has been fixed in C++14 (see library defect 2103).
My question is, is there a way for me to force noexcept upon the default move assignment assignment operator without having to define it myself?
If this is not possible, is there a way I can trick the std::vector<int> into being noexcept move assignable so that noexcept(true) is passed through to my struct?
I believe this has been fixed in C++14 (see library defect 2103).
As a DR that fix should be considered a correction to C++11 and so some C++11 implementations will have already fixed it.
My question is, is there a way for me to force noexcept upon the default move assignment assignment operator without having to define it myself?
For the defaulted move assignment operator to be noexcept you need to make its sub-objects have noexcept move assignment operators.
The most obvious portable way I can think of is to use a wrapper around std::vector which forces the move to be noexcept
template<typename T, typename A = std::allocator<T>>
struct Vector : std::vector<T, A>
{
using vector::vector;
Vector& operator=(Vector&& v) noexcept
{
static_cast<std::vector<T,A>&>(*this) = std::move(v);
return *this;
}
Vector& operator=(const Vector&) = default;
};
Another similar option is to define your own allocator type with the DR 2013 fix and use that:
template<typename T>
struct Allocator : std::allocator<T>
{
Allocator() = default;
template<typename U> Allocator(const Allocator<U>&) { }
using propagate_on_container_move_assignment = true_type;
template<typename U> struct rebind { using other = Allocator<U>; };
};
template<typename T>
using Vector = std::vector<T, Allocator<T>>;
Another option is to use a standard library implementation such as GCC's which implements the resolution to DR 2013 and also makes std::vector's move assignment operator noexcept for other allocator types when it is known that all allocator instances compare equal.
I do not think you can force anything, but you may wrap it:
#include <iostream>
#include <vector>
template <typename T>
struct Wrap
{
public:
Wrap() noexcept
{
new (m_value) T;
}
Wrap(const Wrap& other) noexcept
{
new (m_value) T(std::move(other.value()));
}
Wrap(Wrap&& other) noexcept
{
std::swap(value(), other.value());
}
Wrap(const T& other) noexcept
{
new (m_value) T(std::move(other));
}
Wrap(T&& other) noexcept
{
new (m_value) T(std::move(other));
}
~Wrap() noexcept
{
value().~T();
}
Wrap& operator = (const Wrap& other) noexcept
{
value() = other.value();
return *this;
}
Wrap& operator = (Wrap&& other) noexcept
{
value() = std::move(other.value());
return *this;
}
Wrap& operator = (const T& other) noexcept
{
value() = other;
return *this;
}
Wrap& operator = (T&& other) noexcept
{
value() = std::move(other);
return *this;
}
T& value() noexcept { return *reinterpret_cast<T*>(m_value); }
const T& value() const noexcept { return *reinterpret_cast<const T*>(m_value); }
operator T& () noexcept { return value(); }
operator const T& () const noexcept { return value(); }
private:
typename std::aligned_storage <sizeof(T), std::alignment_of<T>::value>::type m_value[1];
};
struct Foo
{
public:
Foo& operator = (Foo&&) noexcept = default;
std::vector<int>& data() noexcept { return m_data; }
const std::vector<int>& data() const noexcept { return m_data; }
private:
Wrap<std::vector<int>> m_data;
};
int main() {
Foo foo;
foo.data().push_back(1);
Foo boo;
boo = std::move(foo);
// 01
std::cout << foo.data().size() << boo.data().size() << std::endl;
return 0;
}
(Thanks Jonathan Wakely)
Related
Context
I'm writing a class template with three parameters so that it is copy/move constructible, copy/move assignable. The class has two template parameters: (1) a type named tree_t and (2) an enumeration value
enum class bounding_t { bound, unbound };
If (2) is bounding_t::bound then the class needs to have a member const tree_t&; when (2) is bounding_t::unbound, the class should not have it. For this I used conditional inheritance:
template <typename tree_t, bounding_t bound>
using base_impl =
std::conditional_t<is_collection_bound<bound>,
base_tree<tree_t>,
base<tree_t>>;
template <typename tree_t, bounding_t bound>
class arrangement_collection : public base_impl<tree_t, bound> { }
The constructors use std::enable_if_t to construct the parent class appropriately.
Attempts at solving
I tried several things to make the class copy/move assignable and copy/move constructible (I'm not sure I can list all of them without exceeding body size limit), but I failed to make it so that none of the assertions fail, and so that the compiler does not issue any warnings. The MWE below makes four assertions fail, all involving bounding_t::bound. The only change in the code that makes those four assertions not fail is the definition of the copy assignment operator without template parameters (the definition of this operator= in the MWE below has template parameters).
arrangement_collection& operator= (const arrangement_collection& ac) noexcept
{
m_num_nodes = ac.m_num_nodes;
return *this;
}
Without the template parameters, however, I get the following warning that I don't understand:
warning: implicitly-declared ‘constexpr arrangement_collection<free_tree, bounding_t::bound>::arrangement_collection(const arrangement_collection<free_tree, bounding_t::bound>&)’ is deprecated [-Wdeprecated-copy]
../untitled2/main.cpp: In function ‘arrangement_collection<free_tree, bounding_t::bound> proxy_copy()’:
../untitled2/main.cpp:232:29: warning: implicitly-declared ‘constexpr arrangement_collection<free_tree, bounding_t::bound>::arrangement_collection(const arrangement_collection<free_tree, bounding_t::bound>&)’ is deprecated [-Wdeprecated-copy]
232 | const auto ac2 = ac.first;
| ^~~~~
../untitled2/main.cpp:122:33: note: because ‘arrangement_collection<free_tree, bounding_t::bound>’ has user-provided ‘arrangement_collection<tree_t, bound>& arrangement_collection<tree_t, bound>::operator=(const arrangement_collection<tree_t, bound>&) [with tree_t = free_tree; bounding_t bound = bounding_t::bound]’
122 | arrangement_collection& operator= (const arrangement_collection& ac) noexcept
| ^~~~~~~~
I'm using g++ (Ubuntu 11.1.0-1ubuntu1~20.04) 11.1.0 and warning flags -Wall -W.
Question
How can I define the copy/move constructors/assignment operators of this class template so that all assertions succeed without warnings?
Minimal Working Example
The code below contains the class definition, has a few assertions at the end (with std::is_constructible_v<> and many others), and a little main() procedure illustrating a use case.
#include <type_traits>
#include <cinttypes>
#include <utility>
// -----------------------------------------------------------------------------
struct free_tree {
uint64_t get_num_nodes() const noexcept { return 10; }
free_tree() noexcept = default;
free_tree(uint64_t) noexcept { }
free_tree(const free_tree&) noexcept = default;
free_tree(free_tree&&) noexcept = default;
free_tree& operator= (const free_tree&) noexcept = default;
free_tree& operator= (free_tree&&) noexcept = default;
};
struct rooted_tree {
uint64_t get_num_nodes() const noexcept { return 10; }
rooted_tree() noexcept = default;
rooted_tree(uint64_t) noexcept { }
rooted_tree(const rooted_tree&) noexcept = default;
rooted_tree(rooted_tree&&) noexcept = default;
rooted_tree& operator= (const rooted_tree&) noexcept = default;
rooted_tree& operator= (rooted_tree&&) noexcept = default;
};
enum class bounding_t { bound, unbound };
template<bounding_t b>
static constexpr bool is_collection_bound = b == bounding_t::bound;
template<bounding_t b>
static constexpr bool is_collection_unbound = b == bounding_t::unbound;
// -----------------------------------------------------------------------------
template <typename tree_t>
struct base_tree {
base_tree(const tree_t& t) noexcept : m_t(t) { }
~base_tree() noexcept = default;
base_tree(const base_tree& bt) noexcept = default;
base_tree(base_tree&& bt) noexcept = default;
base_tree& operator= (const base_tree& bt) noexcept = default;
base_tree& operator= (base_tree&& bt) noexcept = default;
const tree_t& m_t;
};
template <typename tree_t>
struct base_empty {
base_empty() noexcept = default;
~base_empty() noexcept = default;
base_empty(const base_empty& bt) noexcept = default;
base_empty(base_empty&& bt) noexcept = default;
base_empty& operator= (const base_empty& bt) noexcept = default;
base_empty& operator= (base_empty&& bt) noexcept = default;
};
template <typename tree_t, bounding_t bound>
using base_impl =
std::conditional_t<is_collection_bound<bound>,
base_tree<tree_t>,
base_empty<tree_t>>;
template <typename tree_t, bounding_t bound>
class arrangement_collection : public base_impl<tree_t, bound> {
public:
template <typename t, bounding_t b> using ac = arrangement_collection<t, b>;
public:
// ------------------
// BASIC CONSTRUCTORS
// when bound == bounding_t::bound
template <bounding_t cb = bound, std::enable_if_t<is_collection_bound<cb>, bool> = true>
arrangement_collection(const tree_t& t) noexcept
: base_tree<tree_t>(t), m_num_nodes(t.get_num_nodes())
{ }
// when bound == bounding_t::unbound
template <bounding_t cb = bound, std::enable_if_t<is_collection_unbound<cb>, bool> = true>
arrangement_collection(uint64_t n) noexcept
: base_empty<tree_t>(), m_num_nodes(n)
{ }
// -----------------
// COPY CONSTRUCTORS
// for bound == bounding_t::bound
template <
typename otree_t,
bounding_t cb = bound, std::enable_if_t<is_collection_bound<cb>, bool> = true
>
arrangement_collection(const arrangement_collection<otree_t, bounding_t::bound>& ac) noexcept
: base_tree<tree_t>(ac.m_t), m_num_nodes(ac.m_num_nodes)
{ }
template <
typename otree_t,
bounding_t cb = bound, std::enable_if_t<is_collection_bound<cb>, bool> = true
>
arrangement_collection(const tree_t& t, const arrangement_collection<otree_t, bounding_t::unbound>& ac) noexcept
: base_tree<tree_t>(t), m_num_nodes(ac.m_num_nodes)
{ }
// for bound == bounding_t::unbound
template <
typename otree_t, bounding_t b,
bounding_t cb = bound, std::enable_if_t<is_collection_unbound<cb>, bool> = true
>
arrangement_collection(const arrangement_collection<otree_t, b>& ac) noexcept
: base_empty<tree_t>(), m_num_nodes(ac.m_num_nodes)
{ }
// -------------------------
// COPY ASSIGNMENT OPERATORS
arrangement_collection& operator= (const arrangement_collection& ac) noexcept
{
m_num_nodes = ac.m_num_nodes;
return *this;
}
void set_num_nodes(uint64_t n) noexcept { m_num_nodes = n; }
public:
uint64_t m_num_nodes;
};
namespace static_assertions {
typedef arrangement_collection<free_tree, bounding_t::bound> ac_free_linear_bound;
typedef arrangement_collection<free_tree, bounding_t::unbound> ac_free_linear_unbound;
typedef arrangement_collection<rooted_tree, bounding_t::bound> ac_rooted_linear_bound;
typedef arrangement_collection<rooted_tree, bounding_t::unbound> ac_rooted_linear_unbound;
static_assert(
std::is_constructible_v<ac_free_linear_bound, const free_tree&>
and not std::is_constructible_v<ac_rooted_linear_bound, const free_tree&>
and std::is_constructible_v<ac_rooted_linear_bound, const rooted_tree&>
//
and std::is_constructible_v<ac_free_linear_bound, free_tree>
and not std::is_constructible_v<ac_rooted_linear_bound, free_tree>
and std::is_constructible_v<ac_rooted_linear_bound, rooted_tree>
//
and std::is_constructible_v<ac_free_linear_bound, const free_tree>
and not std::is_constructible_v<ac_rooted_linear_bound, const free_tree>
and std::is_constructible_v<ac_rooted_linear_bound, const rooted_tree>
//
and std::is_constructible_v<ac_free_linear_bound, free_tree&>
and not std::is_constructible_v<ac_rooted_linear_bound, free_tree&>
and std::is_constructible_v<ac_rooted_linear_bound, rooted_tree&>
//
// These classes *can* be constructed from uint64_t since free_tree is
// constructible from uint64_t
and std::is_constructible_v<ac_free_linear_bound, uint64_t&>
and std::is_constructible_v<ac_rooted_linear_bound, uint64_t&>
and std::is_constructible_v<ac_free_linear_bound, uint64_t>
and std::is_constructible_v<ac_rooted_linear_bound, uint64_t>
);
static_assert(
not std::is_constructible_v<ac_free_linear_unbound, const free_tree&>
and not std::is_constructible_v<ac_free_linear_unbound, const rooted_tree&>
and not std::is_constructible_v<ac_rooted_linear_unbound, const free_tree&>
and not std::is_constructible_v<ac_rooted_linear_unbound, const rooted_tree&>
and std::is_constructible_v<ac_free_linear_unbound, uint64_t>
and std::is_constructible_v<ac_rooted_linear_unbound, uint64_t>
);
static_assert(
std::is_copy_constructible_v<ac_free_linear_bound>
and std::is_copy_constructible_v<ac_free_linear_unbound>
and std::is_copy_constructible_v<ac_rooted_linear_bound>
and std::is_copy_constructible_v<ac_rooted_linear_unbound>
);
static_assert(std::is_copy_assignable_v<ac_free_linear_bound>);
static_assert(std::is_copy_assignable_v<ac_rooted_linear_bound>);
static_assert(
std::is_copy_assignable_v<ac_free_linear_unbound>
and std::is_copy_assignable_v<ac_rooted_linear_unbound>
);
static_assert(
std::is_move_constructible_v<ac_free_linear_bound>
and std::is_move_constructible_v<ac_free_linear_unbound>
and std::is_move_constructible_v<ac_rooted_linear_bound>
and std::is_move_constructible_v<ac_rooted_linear_unbound>
);
static_assert(
std::is_move_constructible_v<ac_free_linear_bound>
and std::is_move_constructible_v<ac_free_linear_unbound>
and std::is_move_constructible_v<ac_rooted_linear_bound>
and std::is_move_constructible_v<ac_rooted_linear_unbound>
);
static_assert(
std::is_move_assignable_v<ac_free_linear_unbound>
and std::is_move_assignable_v<ac_rooted_linear_unbound>
);
static_assert(std::is_move_assignable_v<ac_free_linear_bound>);
static_assert(std::is_move_assignable_v<ac_rooted_linear_bound>);
} // -- namespace static_assertions
std::pair<
arrangement_collection<free_tree,bounding_t::bound>,
arrangement_collection<free_tree,bounding_t::unbound>
>
make_arrangement_collection() noexcept
{
free_tree ft;
arrangement_collection<free_tree,bounding_t::bound> acb(ft);
acb.set_num_nodes(10);
arrangement_collection<free_tree,bounding_t::unbound> acu(10);
acu.set_num_nodes(10);
return std::make_pair(std::move(acb), std::move(acu));
}
arrangement_collection<free_tree,bounding_t::bound>
proxy_copy() noexcept
{
const auto ac = make_arrangement_collection();
const auto ac2 = ac.first;
return ac2;
}
arrangement_collection<free_tree,bounding_t::bound>
proxy_move() noexcept
{
const auto ac = make_arrangement_collection();
return std::move(ac.first);
}
int main() {
const auto accopy = proxy_copy();
const auto acmove = proxy_move();
}
A comment on 'Minimal'
The original class template has three template parameters and twice as many lines of code.
The whole code provided is long because it contains the definition of a main() procedure, the two classes that and the assertions, but the actual class template is small.
Your class currently doesn't have a copy constructor, since the copy constructor cannot be a template (and the ones you defined will never be used). Generating an implicitly-defined copy constructor is deprecated for types that have a user-defined copy assignment operator.
However, the default definition of the copy constructor does what you want (copying a base_empty<tree_t> has no overhead), so just arrangement_collection(const arrangement_collection&) = default; will work.
You can also rewrite this so the constructors are in base_tree<tree_t> and base_empty<tree_t>, so in arrangement_collection you can just inherit the constructors with using base_impl<tree_t, bound>::base_impl, avoiding all the enable_if SFINAE
I have the following class:
class Data;
class A
{
public:
A(Data& _data) : data(_data) {}
Data& getData() {return data;}
const Data& getData() const {return data;}
private:
Data& data;
};
Now imagine I need to keep not one, but multiple instances of Data. I keep them in a vector of reference wrappers, but I would also like to keep the const correctness: pass the data as unmodifiable in const context.
class A
{
public:
void addData(Data& _data) {data.push_back(std::ref(_data));}
const std::vector<std::reference_wrapper<Data>>& getData() {return data;}
//doesn't compile
//const std::vector<std::reference_wrapper<const Data>>& getData() const {return data;}
private:
std::vector<std::reference_wrapper<Data>> data;
}
How to implement this without having physical copying of the data? I.e. I don't want to return a copy of the vector by value and I don't want to keep two separate vectors in class A. Both are performance-impacting solutions for what is basically just a semantic problem.
Here's a const propagating reference_wrapper, based on cppreference's possible implementation
#include <utility>
#include <functional>
#include <type_traits>
namespace detail {
template <class T> T& FUN(T& t) noexcept { return t; }
template <class T> void FUN(T&&) = delete;
}
template <class T>
class reference_wrapper {
public:
// types
typedef T type;
// construct/copy/destroy
template <class U, class = decltype(
detail::FUN<T>(std::declval<U>()),
std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>> && !std::is_same_v<reference_wrapper<const T>, std::remove_cvref_t<U>>>()
)>
reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<T>(std::forward<U>(u))))
: _ptr(std::addressof(detail::FUN<T>(std::forward<U>(u)))) {}
reference_wrapper(reference_wrapper&) noexcept = default;
reference_wrapper(reference_wrapper&&) noexcept = default;
// assignment
reference_wrapper& operator=(reference_wrapper& x) noexcept = default;
reference_wrapper& operator=(reference_wrapper&& x) noexcept = default;
// access
operator T& () noexcept { return *_ptr; }
T& get() noexcept { return *_ptr; }
operator const T& () const noexcept { return *_ptr; }
const T& get() const noexcept { return *_ptr; }
template< class... ArgTypes >
std::invoke_result_t<T&, ArgTypes...>
operator() ( ArgTypes&&... args ) {
return std::invoke(get(), std::forward<ArgTypes>(args)...);
}
template< class... ArgTypes >
std::invoke_result_t<const T&, ArgTypes...>
operator() ( ArgTypes&&... args ) const {
return std::invoke(get(), std::forward<ArgTypes>(args)...);
}
private:
T* _ptr;
};
template <class T>
class reference_wrapper<const T> {
public:
// types
typedef const T type;
// construct/copy/destroy
template <class U, class = decltype(
detail::FUN<const T>(std::declval<U>()),
std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>> && !std::is_same_v<reference_wrapper<T>, std::remove_cvref_t<U>>>()
)>
reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<const T>(std::forward<U>(u))))
: _ptr(std::addressof(detail::FUN<const T>(std::forward<U>(u)))) {}
reference_wrapper(const reference_wrapper<T>& o) noexcept
: _ptr(std::addressof(o.get())) {}
reference_wrapper(const reference_wrapper&) noexcept = default;
reference_wrapper(reference_wrapper&&) noexcept = default;
// assignment
reference_wrapper& operator=(const reference_wrapper& x) noexcept = default;
reference_wrapper& operator=(reference_wrapper&& x) noexcept = default;
// access
operator const T& () const noexcept { return *_ptr; }
const T& get() const noexcept { return *_ptr; }
template< class... ArgTypes >
std::invoke_result_t<const T&, ArgTypes...>
operator() ( ArgTypes&&... args ) const {
return std::invoke(get(), std::forward<ArgTypes>(args)...);
}
private:
const T* _ptr;
};
// deduction guides
template<class T>
reference_wrapper(T&) -> reference_wrapper<T>;
You can then add const qualified access via span.
class A
{
public:
void addData(Data& _data) {data.emplace_back(_data);}
std::span<reference_wrapper<Data>> getData() { return { data.data(), data.size() }; }
std::span<const reference_wrapper<Data>> getData() const { return { data.data(), data.size() }; }
private:
std::vector<reference_wrapper<Data>> data;
}
Note that you can't copy or move the const reference_wrapper<Data>s from the second getData, and there is only access to const Data &.
Consider the visitor pattern :
struct ConstVisitor {
virtual ~ConstVisitor() = default;
virtual bool visit(const Data & data) = 0;//returns true if search should keep going on
};
void A::accept(ConstVisitor & visitor) const;
This way it does not matter to the outside world what kind of container Data is stored in (here a std::vector). The visitor pattern is very similar to an Enumerator in C#.
Consider the following C++ code with my failed attempt to avoid preference of non-template copy&move constructors and assignment operators:
template<typename T> class A {
public:
A() { /* implementation here */ }
// Remove from the overloads the default copy&move constructors and assignment operators
A(const A&) = delete;
A& operator=(const A&) = delete;
A(A&&) = delete;
A& operator=(A&&) = delete;
// I want these to be used e.g. by std::vector
template<typename U> A(const A<U>& fellow) { /* implementation here */ }
template<typename U> A& operator=(const A<U>& fellow) { /* implementation here */ }
template<typename U> A(A<U>&& fellow) { /* implementation here */ }
template<typename U> A& operator=(A<U>&& fellow) { /* implementation here */ }
};
However, I get the following error
attempting to reference a deleted function
when trying to push A items to a vector or simply copy-construct like:
A<int> a1{};
A<int> a2(a1);
UPDATE1: I need template copy&move constructors and assignment operators, because the template argument really just controls some caching, so A<T1> can be safely assigned to A<T2>.
You can make compiler happy by declaring deleted copy constructor / assignment operator with alternative signature which will not cause this overload to be selected but will prevent generation of constructor / assignment operator by compiler:
template<typename T> class A
{ public:
A() { /* implementation here */ }
// Remove from the implicit declaration of the default copy&move constructors and assignment operators
A(A volatile const &) = delete;
A & operator =(A volatile const &) = delete;
// I want these to be used e.g. by std::vector
template<typename U> A(A<U> const & fellow) { /* implementation here */ }
template<typename U> A & operator =(A<U> const & fellow) { /* implementation here */ return *this;}
template<typename U> A(A<U> && fellow) { /* implementation here */ }
template<typename U> A & operator =(A<U> && fellow) { /* implementation here */ return *this; }
};
int main()
{
A<int> a1{};
A<int> a2{a1};
return 0;
}
online compiler
15.8.1 Copy/move constructors [class.copy.ctor]
1. A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments
A minimal example of a copy constructor that delegate the execution to the template constructor using a second unused (and defaulted) argument
#include <iostream>
template <typename T>
struct A
{
A()
{ }
A (A const & a0) : A{a0, 0}
{ }
template<typename U>
A (A<U> const &, int = 0)
{ std::cout << "template constructor" << std::endl; }
};
int main()
{
A<int> a0;
A<int> a1{a0};
}
-- EDIT --
The OP asks
What about operator=? Trying to add a dummy parameter gives compiler errors binary 'operator =' has too many parameters and 'operator =' cannot have default parameters
For operator=() I propose to "delegate" (not in the meaning of delegating constructor, in this case) both operators to a normal method; a template one.
Something as
template <typename U>
A & assign (A<U> const &)
{ /* do assignment */ return *this; }
A & operator= (A const & a0)
{ return assign(a0); }
template <typename U>
A & operator= (A<U> const & a0)
{ return assign(a0); }
Maybe the assign() method can be a private one.
Or better, as suggested by Jarod42 (thanks), directly calling the template operator from the not-template one
template <typename U>
A & operator= (A<U> const & a0)
{ /* do assignment */ return *this; }
A & operator= (A const & a0)
{ return operator=<T>(a0); }
I discovered my compressed_tuple<T1, T2> class doesn't compile when either T1 or T2 don't have a copy and/or move constructor with the error "attempting to reference a deleted function". That error refers to usage of a deleted copy constructor in the copy constructor of compressed_tuple. I understand what the error is, why I get it, and how to fix it. What I don't understand is how to do it without excessive specialization(elaborated on below).
compressed_tuple is my name for a pair that uses EBO via specialization to minimize its size when at least one value is empty and derivable. i.e. std::unique_ptr is typically implemented with this mechanism to prevent empty deleters from affecting its size(otherwise plain std::unique_ptr would be larger than a pointer).
TL:DR Skip to the bottom, where I ask the actual question. Everything leading up to it is informational context.
Typically I would use specialization and call it a day, similar to this:
template<class T>
constexpr bool is_copyable_v =
std::is_copy_constructible_v<T> && std::is_copy_assignable_v<T>;
template<class T>
constexpr bool is_movable_v =
std::is_move_constructible_v<T> && std::is_move_assignable_v<T>;
template<class T>
class example_base
{
public:
T value;
protected:
example_base() = default;
example_base(const example_base&) = default;
example_base(example_base&&) = default;
~example_base() = default;
inline example_base& operator=(const example_base& source)
noexcept(std::is_nothrow_copy_assignable_v<T>)
{
static_assert(is_copyable_v<T>, "T must be copyable.");
if constexpr (is_copyable_v<T>) {
value = source.value;
}
return *this;
}
inline example_base& operator=(example_base&& source)
noexcept(std::is_nothrow_move_assignable_v<T>)
{
static_assert(is_movable_v<T>, "T must be movable.");
if constexpr (is_movable_v<T>) {
value = std::move(source.value);
}
return *this;
}
};
// T is both copyable and movable.
template<
class T,
bool = is_copyable_v <T>,
bool = is_movable_v<T>>
class example final : example_base<T>
{
using base = example_base<T>;
public:
example() = default;
inline example(const example& source)
noexcept(std::is_nothrow_copy_constructible_v<T>) :
base(source) {}
inline example(example&& source)
noexcept(std::is_nothrow_move_constructible_v<T>) :
base(std::move(source)) {}
inline example& operator=(const example& source)
noexcept(std::is_nothrow_copy_assignable_v<T>)
{
return static_cast<example&>(base::operator=(source));
}
inline example& operator=(example&& source)
noexcept(std::is_nothrow_move_assignable_v<T>)
{
return static_cast<example&>(base::operator=(std::move(source)));
}
};
// T is copyable, but not movable.
template<class T>
class example<T, true, false> final : public example_base<T>
{
using base = example_base<T>;
public:
example() = default;
inline example(const example& source)
noexcept(std::is_nothrow_copy_constructible_v<T>) :
base(source) {}
example(example&&) = delete;
inline example& operator=(const example& source)
noexcept(std::is_nothrow_copy_assignable_v<T>)
{
return static_cast<example&>(base::operator=(source));
}
example& operator=(example&&) = delete;
};
// T isn't copyable, but is movable.
template<class T>
class example<T, false, true> final : public example_base<T>
{
using base = example_base<T>;
public:
example() = default;
inline example(example&& source)
noexcept(std::is_nothrow_move_constructible_v<T>) :
base(std::move(source)) {}
example(const example&) = delete;
inline example& operator=(example&& source)
noexcept(std::is_nothrow_move_assignable_v<T>)
{
return static_cast<example&>(base::operator=(std::move(source)));
}
example& operator=(const example&) = delete;
};
// T is neither copyable nor movable.
template<class T>
class example<T, false, false> final : public example_base<T>
{
public:
example() = default;
example(const example&) = delete;
example(example&&) = delete;
example& operator=(const example&) = delete;
example& operator=(example&&) = delete;
};
Which works fine but blows up exponentially if any other template parameters require further specialization.
compressed_tuple is quite large with all of its specializations, so I have omitted most of it:
// T1 is empty and inheritable, but T2 isn't, so derive from T1 and store T2.
// Handles both <..., true, true> and <..., true, false>.
template<class T1, class T2,
bool = std::is_empty_v<T1> && !std::is_final_v<T1>,
bool = std::is_empty_v<T2> && !std::is_final_v<T2>>
class compressed_tuple final : private T1
{
private:
using base = T1;
T2 second;
public:
compressed_tuple(const compressed_tuple& source)
noexcept(
std::is_nothrow_copy_constructible_v<T1> &&
std::is_nothrow_copy_constructible_v<T2>) :
base(source),
second(source.second) {}
/*...*/
};
// T2 is empty and inheritable, but T1 isn't, so derive from T2 and store T1.
template<class T1, class T2>
class compressed_tuple<T1, T2, false, true> final : private T2
{
private:
using base = T2;
T1 first;
public:
compressed_tuple(const compressed_tuple& source)
noexcept(
std::is_nothrow_copy_constructible_v<T1> &&
std::is_nothrow_copy_constructible_v<T2>) :
base(source),
first(source.first) {}
/*...*/
};
// Neither T1 nor T2 are empty and derivable, so store both.
template<class T1, class T2>
class compressed_tuple<T1, T2, false, false> final
{
private:
T1 first;
T2 second;
public:
compressed_tuple(const compressed_tuple& source)
noexcept(
std::is_nothrow_copy_constructible_v<T1> &&
std::is_nothrow_copy_constructible_v<T2>) :
first(source.first),
second(source.second) {}
/*...*/
};
What I'm trying to do could be achieved with the following:
template<
class T,
bool = is_copyable_v<T>,
bool = is_movable_v<T>,
bool = std::is_empty_v<T1> && !std::is_final_v<T1>,
bool = std::is_empty_v<T2> && !std::is_final_v<T2>>
class compressed_tuple final { /*...*/ };
// ...specialize on all valid combinations...
Though it would require a large number of specializations. What I'm looking for is an alternative if possible.
To my understanding, SFINAE isn't an option for this. C++20 constraints would solve this very problem, but as of the time of this writing, it'll be quite some time before mainstream compilers are C++20 compliant. How can conditional copy and move constructors be implemented in C++17 without specializations or a large number of specializations?
Two common approaches are:
Default the special member functions, then arrange for them to become deleted (e.g., by using a special base class for this purpose).
"Eric's trick":
Foo& operator=(std::conditional_t<can_copy, Foo, nonesuch> const& rhs) {
// implement
}
Foo& operator=(std::conditional_t<!can_copy, Foo, nonesuch> const&) = delete;
How does this templated operator() work in reference_wrapper implementation
template <class T>
class reference_wrapper {
public:
// types
typedef T type;
// construct/copy/destroy
reference_wrapper(T& ref) noexcept : _ptr(std::addressof(ref)) {}
reference_wrapper(T&&) = delete;
reference_wrapper(const reference_wrapper&) noexcept = default;
// assignment
reference_wrapper& operator=(const reference_wrapper& x) noexcept = default;
// access
operator T& () const noexcept { return *_ptr; }
T& get() const noexcept { return *_ptr; }
here it goes:
template< class... ArgTypes >
typename std::result_of<T&(ArgTypes&&...)>::type
operator() ( ArgTypes&&... args ) const {
return std::invoke(get(), std::forward<ArgTypes>(args)...);
}
why do we need operator() anyway? how it works?
what is the return content "result_of::type"?
what is (ArgTypes && ..) ??
invoke(get) ???
this code looks like C++ from another planet :)
private:
T* _ptr;
};
why do we need operator() anyway? how it works?
Suppose following context
int foo(int bar)
{
return bar + 5;
}
int main()
{
std::reference_wrapper<int(int)> ref = foo;
ref(5);
}
ref(5) calls operator() of reference wrapper. If it wouldn't be there, it would not work, because user defined conversion wouldn't happen in this case.
operator() returns std::result_of<T&(ArgTypes&&...), which is return value of the function stored and std::invoke call such function and forwards parameters to it.