Generic constructor template called instead of copy/move constructor - c++

I've designed a simpler wrapper class that adds a label to an object, with the intent of being implicitly convertible/able to replace the wrapped object.
#include <string>
#include <type_traits>
#include <utility>
template < typename T, typename Key = std::string >
class myTag{
T val;
public:
Key key;
template < typename... Args,
typename = typename std::enable_if< std::is_constructible< T, Args... >::value >::type >
myTag(Args&&... args) :
val(std::forward< Args >(args)...) {
std::cout << "forward ctor" << std::endl;
}
myTag(const myTag& other) :
key(other.key), val(other.val) {
std::cout << "copy ctor" << std::endl;
}
myTag(myTag&& other):
key(other.key), val(other.val) {
std::cout << "move ctor" << std::endl;
}
operator T&() { return val; }
operator const T&() const { return val; }
};
int main(int argc, char const *argv[]) {
myTag< float > foo(5.6); // forward ctor
myTag< float > bar(foo); // forward ctor
return 0;
}
However, I am having trouble properly declaring & defining the copy/move constructor. I declared a generic constructor overload template that forwards its arguments to the underlying type, as long as such construction is possible. However, due to the implicit conversion operators, it is capturing every instantiation of myTag, effectively shadowing the copy/move constructors.
The point of non-default copy/move semantics was to copy/move the key value vs default-initializing it with constructor template.
How do I make the compiler prefer/give precedence to the explicit copy/move constructors vs the generic overload? Is there any additional SFINAE check alternative to is_constructible<> that avoids implicit conversions?
EDIT: I should add that I'm looking for a C++14 solution.

It isn't shadowing the copy and move constructors. It is just beating them in overload resolution in some cases.
If you pass a myTag<float>&, the forwarding constructor is used.
If you pass a const myTag<float>& the copy constructor is used.
If you pass a myTag<float>&&, the move constructor is used.
If you pass a const myTag<float>&&, the forwarding constructor is used.
The copy and move constructors are not templates, so they will win against a template of the same signature. But the forwarding constructor can be a better match in the cases where the deduced signature differs from the copy and move constructors.
The idiomatic way to handle this is to remove the forwarding constructor from consideration based on the decayed type of the arguments:
template <typename... Args>
constexpr myTag(Args&&... args)
requires
((sizeof...(Args) != 1 && std::is_constructible_v<T, Args...>) ||
(sizeof...(Args) == 1 && std::is_convertible_v<Args..., T> && !std::is_same_v<myTag, std::decay_t<Args>...>))

Related

Conversion constructor strangely not competing with copy constructor

Implementing a copy constructor deletes the default move constructor in C++.
Only compiler generated copy and move constructors are trivial.
Created a templated conversion constructor from any type to the current type.
#include <format>
#include <iostream>
#include <type_traits>
template <typename T = int>
class Element {
public:
T value;
Element(const T value_) noexcept : value(value_) {};
// Here is the conversion constructor
template <typename TT>
Element(const Element<TT> &element) noexcept : value(element.value) {
std::cout << std::format(
"Element<{}>(const {}& {})\n",
typeid(T).name(), typeid(TT).name(), element.value
);
}
// uncommenting this breaks the assertions coming next
// Element(const Element &element) noexcept : value(element.value) {};
};
static_assert(std::is_trivially_move_constructible<Element<>>::value);
static_assert(std::is_trivially_copy_constructible<Element<>>::value);
// how it behaves
void foo_int(Element<int> element) {
std::cout << std::format("foo_int: {}\n", element.value);
}
void foo_double(Element<double> element) {
std::cout << std::format("foo_double: {}\n", element.value);
}
int main() {
Element<int> int_element {1};
Element<double> double_element {1.5};
foo_int(int_element);
foo_double(int_element);
// uncommenting doesn't compile - narrowing conversion
// foo_int(double_element);
foo_double(double_element);
return 0;
}
Demo in Compiler Explorer
Can someone explain to me why this conversion constructor is not matched with T == TT. In that case only the copy / move constructor is called.
Then I thought maybe I can call it manually with auto a = Element<int>::Element<int>(int_element);. But that gives an error: "obsolete declaration style".
It seems to be treated like a normal constructor, and only considered after the other special member functions.
Can someone explain me the rules or where I can read more about this behavior?
When type T is the same as TT, then you are making a copy. The standard explicitly states that the copy constructor is not a template:
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 ([dcl.fct.default]).
https://eel.is/c++draft/class.copy.ctor#1
Also, in C++, a non-template is always preferred over a template if it's an exact match. Given there is a real copy constructor, and your templated conversion constructor, in copying contexts the copy constructor would be picked. The same thing would happen for ordinary functions too:
template <typename T>
void foo(T);
void foo(int);
foo(123); // will call the non-template version (since it's an exact match)

std::reference_wrapper, constructor implementation explaination

I have been trying to understand the implementation of std::reference_wrapper, from here, which is as follows:
namespace detail {
template <class T> constexpr 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>>>()
)>
constexpr 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(const reference_wrapper&) noexcept = default;
// assignment
reference_wrapper& operator=(const reference_wrapper& x) noexcept = default;
// access
constexpr operator T& () const noexcept { return *_ptr; }
constexpr T& get() const noexcept { return *_ptr; }
template< class... ArgTypes >
constexpr std::invoke_result_t<T&, ArgTypes...>
operator() ( ArgTypes&&... args ) const {
return std::invoke(get(), std::forward<ArgTypes>(args)...);
}
private:
T* _ptr;
};
Although the implementation of std::reference_wrapper has been discussed hereand here,but none of it discusses the constructor implementation which i am confused about. My confusions are :
1.) Constructor is a template function , taking a type param (U) different from the template class param T. I have seen member functions of a class being template functions and depending on different type params then the type param of the template class, but i can't think how it is works here. There is a related question here, but i am not able to relate it with my confusion.
2.)I see the second type parameter in the constructor is further used to sfinae out something, but i did not understand howdetail::FUN<T>(std::declval<U>()) is evaluated.
Can someone please explain ?
Edit: This is an example added from microsoft.docs. A snippet of the
example is:
int i = 1;
std::reference_wrapper<int> rwi(i); // A.1
rwi.get() = -1;
std::cout << "i = " << i << std::endl; //Prints -1
With the implementation of reference_wrapper , and from A.1, how is the constructor of the reference_wrapper called ? Assuming that detail::FUN<T>(std::declval<U>() will be called with detail::FUN<T>(std::declval<int>(), should be a substitution failure because of the deleted overload(Assuming std::declval<int> will be read as an rvalue reference to int). What am i missing here ?
It's a technique you can use when you want the behaviour of the "forwarding reference", U&& in this case, but at the same time restrict what can bind to it.
Deduction of T is aided by the deduction guide provided below. The detail::FUN<T>(std::declval<U>()) is there to ensure that the constructor is disabled when U is deduced to be an rvalue reference, by selecting the deleted overload and producing an invalid expression. It is also disabled if the U is another reference wrapper, in which case the copy constructor should be selected.
Here are a few examples of valid and invalid reference_wrappers:
int i = 1;
// OK: FUN<int>(std::declval<int&>()) is valid
std::reference_wrapper<int> rwi(i);
// error: forming pointer to reference
std::reference_wrapper<int&> rwi2(i);
// OK, uses deduction guide to find T = int
std::reference_wrapper rwi3(i);
std::reference_wrapper rwi4(++i);
// error: cannot deduce T, since there is no deduction guide for
// rvalue reference to T
std::reference_wrapper rwi5(std::move(i));
// error: substitution failure of FUN<int>(int&&)
std::reference_wrapper<int> rwi6(std::move(i));
std::reference_wrapper<int> rwi7(i++);
std::reference_wrapper<int> rwi8(i + i);
std::reference_wrapper<int> rwi9(2);
As you can see, the call to the deleted FUN<T>(T&&) only comes into play in the last 4 cases: when you explicitly specify T, but attempt to construct from an rvalue.

non-template std::reference_wrapper assignment operator and template constructor

In the C++ 20 Standard the constructor of the class template std::reference_wrapper is a template.
template<class U>
constexpr reference_wrapper(U&&) noexcept(see below );
while the assignment operator is not a template
constexpr reference_wrapper& operator=(const reference_wrapper& x) noexcept;
What is the reason that this difference (template and non-template) between these special member functions exists?
On the other hand, I tried the following progarm using Visual C++ 2019.
#include <iostream>
#include <functional>
struct A
{
void f() const { std::cout << "A::f()\n"; }
virtual void g() const { std::cout << "A::g()\n"; }
};
struct B : A
{
void f() const { std::cout << "B::f()\n"; }
void g() const override { std::cout << "B::g()\n"; }
};
int main()
{
B b;
std::reference_wrapper<A> r( b );
r.get().f();
r.get().g();
r = std::reference_wrapper<B>( b );
}
and the compiler did not issue an error message relative to the assignment operator.
Is it a bug of the compiler or have I missed something?
If std::reference_wrapper<T> had a single constructor accepting T&, then it would lead to bugs like std::reference_wrapper<const T> being able to bind to temporaries of type T. So, originally there were two constructors (other than the copy constructor): one taking T&, and another taking T&&, which was defined as deleted, ensuring that a compile error would occur.
However, it was pointed out that this is not really what we want: it would be better if std::reference_wrapper<T> would have no constructor at all that accepts an rvalue of type T, as opposed to a deleted constructor. This is LWG 2993. (You'll notice that this issue is mentioned at the bottom of the cppreference page.) Thus, the constructor was changed to a template that is SFINAE-disabled as appropriate.
Once these issues are solved for the constructor, the assignment operator only needs to have a single overload taking reference_wrapper. The conversion issues will be handled by the constructor logic when the compiler forms the implicit conversion sequence.

Why is template constructor preferred to copy constructor?

#include <iostream>
struct uct
{
uct() { std::cerr << "default" << std::endl; }
uct(const uct &) { std::cerr << "copy" << std::endl; }
uct( uct&&) { std::cerr << "move" << std::endl; }
uct(const int &) { std::cerr << "int" << std::endl; }
uct( int &&) { std::cerr << "int" << std::endl; }
template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
};
int main()
{
uct u1 ; // default
uct u2( 5); // int
uct u3(u1); // template, why?
}
coliru
Template overload of the constructor fits to both declarations (u2 and u3). But when int is passed to the constructor, a non-template overload is chosen. When the copy constructor is called, a template overload is chosen. As far as I know, a non-template function is always preferred to a template function during overload resolution. Why is the copy constructor handled differently?
As far as I know non-template function is always preferred to template function during overload resolution.
This is true, only when the specialization and the non template are exactly the same. This is not the case here though. When you call uct u3(u1) The overload sets gets
uct(const uct &)
uct(uct &) // from the template
Now, since u1 is not const it would have to apply a const transformation to call the copy constructor. To call the template specialization it needs to do nothing since it is an exact match. That means the template wins as it is the better match.
To stop this one thing you can do is use SFINAE to limit the template function to only be called when T is not a uct. That would look like
template <typename T, std::enable_if_t<!std::is_same_v<uct, std::decay_t<T>>, bool> = true>
uct(T &&) { std::cerr << "template" << std::endl; }
When copy constructor is tried to be called, template overload is
chosen. As far as I know non-template function is always preferred to
template function during overload resolution. Why is copy constructor
handled differently?
template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
// ^^
The reason the templated version gets picked is because the compiler is able
to generate a constructor with signature (T &) which fits better and therefore is chosen.
If you changed the signature from uct u1 to const uct u1 then it would fit the copy constructor (since u1 is not const to begin with).
If you changed the signature from uct(const uct &) to uct(uct&) it would be a better fit and it would choose that over the templated version.
Also, the uct(uct&&) would be chosen if you had used uct u3(std::move(u1));
To fix this you can use SFINAE to disable the overload when T is the same as uct:
template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, uct>>>
uct(T&&)
{
std::cerr << "template" << std::endl;
}
The problem is that the template constructor has no the qualification const while the non-template copy constructor has the qualifier const in its parameter. If you will declare the object u1 as a const object then the non-template copy constructor will be called.
From the C++ STandard (7 Standard conversions)
1 Standard conversions are implicit conversions with built-in meaning.
Clause 7 enumerates the full set of such conversions. A standard
conversion sequence is a sequence of standard conversions in the
following order:
(1.4) — Zero or one qualification conversion
So the copy constructor needs one standard conversion while the template constructor sies not require such a conversion.

How do I get the copy constructor called over a variadic constructor?

In the following code, the variadic constructor is called twice. How can I get the copy constructor to be called instead of the single argument version of the variadic constructor when appropriate?
#include <iostream>
struct Foo
{
Foo(const Foo &)
{
std::cout << "copy constructor\n";
}
template<typename... Args>
Foo(Args&&... args)
{
std::cout << "variadic constructor\n";
}
std::string message;
};
int main()
{
Foo f1;
Foo f2(f1); // this calls the variadic constructor, but I want the copy constructor.
}
This actually has nothing to do with the fact that the constructor is variadic. The following class with a non-variadic constructor template exhibits the same behavior:
struct Foo
{
Foo() { }
Foo(const Foo& x)
{
std::cout << "copy constructor\n";
}
template <typename T>
Foo(T&& x)
{
std::cout << "template constructor\n";
}
};
The problem is that the constructor template is a better match. To call the copy constructor, a qualification conversion is required to bind the non-const lvalue f1 to const Foo& (the const qualification must be added).
To call the constructor template, no conversions are required: T can be deduced to Foo&, which after reference collapsing (Foo& && -> Foo&), gives the parameter x type Foo&.
You can work around this by providing a second copy constructor that has a non-const lvalue reference parameter Foo&.
Just provide an exact-match overload, i.e. one with a non-const Foo&, in addition to the conventional copy constructor. Then you can delegate the call via an explicit cast:
Foo(Foo& other) : Foo{const_cast<Foo const&>(other)} { }