Reliable conditional copy and move constructors in template class with specializations - c++

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;

Related

How to make a class template copy/move constructible/assignable with `std::enable_if_t`

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

How to utilize template copy&move constructor and assignment operator?

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); }

C++ template class operator overloading with the same signatures

A simple C++ OO question regrading templates and operator overloading: In the following class, I have overloaded the index operator twice:
template<class A, class B>
class test
{
A a1;
B a2;
public:
A& operator[](const B&);
B& operator[](const A&);
};
Now, if I instantiate an object of this template class with the same typenames:
test<int, int> obj;
calling the index operator will result in an error, because the two overloaded functions will have the same signatures.
Is there any way to resolve this issue?
Sorry, if this is a basic question. I am still learning!
You can add a partial specialization:
template<class A>
class test<A, A>
{
A a1, a2;
public:
A& operator[](const A&);
};
You can avoid this issue and make the code more robust and expressive by converting the index to some other type that clarifies what the user wants. Usage would be like this:
bidirectional_map<int, int> myTest;
int& valueFor1 = myTest[Key{1}];
int& key1 = myTest[Value{valueFor1}];
Implemented like this:
template<class TKey>
struct Key { const TKey& key; };
template<class TValue>
struct Value { const TValue& value; };
// Deduction guides (C++17), or use helper functions.
template<class TValue>
Value(const TValue&) -> Value<TValue>;
template<class TKey>
Key(const TKey&) -> Key<TKey>;
template<class TKey, class TValue>
class bidirectional_map
{
TKey a1; // Probably arrays
TValue a2; // or so?
public:
TValue & operator[](Key<TKey> keyTag) { const TKey & key = keyTag.key; /* ... */ }
TKey & operator[](Value<TValue> valueTag) { const TValue& value = valueTag.value; /* ... */ }
};
Now, Key and Value are popular names so having them "taken up" by these auxiliary functions is not the best. Also, this is all just a pretty theoretical exercise, because member functions are of course a much better fit for this task:
template<class TKey, class TValue>
class bidirectional_map
{
TKey a1; // Probably arrays
TValue a2; // or so?
public:
TValue& getValueForKey(const TKey& key) { /* ... */ }
TKey& getKeyForValue(const TValue& value) { /* ... */ }
};
In C++2a, you might use requires to "discard" the function in some case:
template<class A, class B>
class test
{
A a1;
B a2;
public:
A& operator[](const B&);
B& operator[](const A&) requires (!std::is_same<A, B>::value);
};
Demo
Here is an example solution using if constexpr that requires C++17:
#include <type_traits>
#include <cassert>
#include <string>
template <class A, class B>
class test
{
A a1_;
B b1_;
public:
template<typename T>
T& operator[](const T& t)
{
constexpr bool AequalsB = std::is_same<A,B>();
constexpr bool TequalsA = std::is_same<T,A>();
if constexpr (AequalsB)
{
if constexpr (TequalsA)
return a1_; // Can also be b1_, same types;
static_assert(TequalsA, "If A=B, then T=A=B, otherwise type T is not available.");
}
if constexpr (! AequalsB)
{
constexpr bool TequalsB = std::is_same<T,B>();
if constexpr (TequalsA)
return a1_;
if constexpr (TequalsB)
return b1_;
static_assert((TequalsA || TequalsB), "If A!=B, then T=A || T=B, otherwise type T is not available.");
}
}
};
using namespace std;
int main()
{
int x = 0;
double y = 3.14;
string s = "whatever";
test<int, int> o;
o[x];
//o[y]; // Fails, as expected.
//o[s]; // Fails, as expected
test<double, int> t;
t[x];
t[y];
//t[s]; // Fails, as expected.
return 0;
};

Template specialization with a specific templated class

Given the following simple C++ class:
using namespace std;
template<class T1>
class ValueWrapper {
private:
T1 value_;
public:
ValueWrapper() {}
ValueWrapper(const T1& value) {
value_ = value;
}
ValueWrapper(const ValueWrapper<T1> &wrapper) {
value_ = wrapper.value_;
}
ValueWrapper& Set(const T1& value) {
value_ = value;
return *this;
}
T1 Get() const {
return value_;
}
};
I was trying to create a simple shared_ptr wrapper for that class (ultimately allowing the developer to use the class without the dereferencing operator if desired). While I've seen a few examples of wrapping a shared_ptr, I couldn't find any that also used a specialization for a templated class.
Using the class above, I created a ValueShared class which derives from shared_ptr:
template<class T1>
class ValueShared : public shared_ptr<T1> {
public:
ValueShared& operator =(const T1& rhs) {
// nothing to do in base
return *this;
}
};
Then, I created a custom make_shared_value function:
//
// TEMPLATE FUNCTION make_shared
template<class T1, class... Types> inline
ValueShared<T1> make_shared_value(Types&&... Arguments)
{ // make a shared_ptr
_Ref_count_obj<T1> *_Rx = new _Ref_count_obj<T1>(_STD forward<Types>(Arguments)...);
ValueShared<T1> _Ret;
_Ret._Resetp0(_Rx->_Getptr(), _Rx);
return (_Ret);
}
But, here's the problem code:
template<class T1, class ValueWrapper<T1>>
class ValueShared<ValueWrapper<T1>> : public shared_ptr<ValueWrapper<T1>>{
public:
ValueShared& operator =(const ValueWrapper<T1>& rhs) {
auto self = this->get();
self.Set(rhs->Get());
return *this;
}
};
I wanted to provide a specialization of the equals operator here that was specialized to the ValueWrapper class (so that it would Get/Set the value from the right hand side value).
I've tried a few things, but the current error is:
error C2943: 'ValueWrapper<T1>' : template-class-id redefined
as a type argument of a template
Maybe this isn't the proper approach, or maybe it's not possible?
Following should remove your error:
template<class T1>
class ValueShared<ValueWrapper<T1>> : public shared_ptr<ValueWrapper<T1>> {
public:
ValueShared& operator =(const ValueWrapper<T1>& rhs)
{
auto self = this->get();
self->Set(rhs.Get());
return *this;
}
};

Can I force a default special member function to be noexcept?

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)