Ambiguous constructor taking std::reference_wrapper when compiling with -pedantic
Asked Answered
T

2

8

I have a class with both a copy constructor and a constructor taking a std::reference_wrapper:

#include <functional>
#include <iostream>

class Class {
public:
    Class() {
        std::cout << "Class()" << std::endl;
    }
    Class(Class const &) {
        std::cout << "Class(Class const &)" << std::endl;
    }
    Class(std::reference_wrapper<Class>) {
        std::cout << "Class(std::reference_wrapper<Class>)" << std::endl;
    }
    Class(std::reference_wrapper<const Class>) {
        std::cout << "Class(std::reference_wrapper<const Class>)" << std::endl;
    }
};

int main() {
    Class a;
    Class b = a;
    Class c = std::ref(a);
    Class d = std::cref(a);
}

When compiled normally (g++ --std=c++17 test.cpp) this works as desired, calling the four constructors in sequence:

$ ./a.exe
Class()
Class(Class const &)
Class(std::reference_wrapper<Class>)
Class(std::reference_wrapper<const Class>)

However, compiling with -pedantic (i.e., g++ --std=c++17 -pedantic test.cpp) results in the following error (and another equivalent one for the std::cref):

test.cpp:23:22: error: conversion from 'std::reference_wrapper<Class>' to 'Class' is ambiguous
  Class c = std::ref(a);
                      ^
note: candidate: std::reference_wrapper<_Tp>::operator _Tp&() const [with _Tp = Class]
note: candidate: Class::Class(std::reference_wrapper<Class>)

Why is this (i.e., how am I violating the standard, answered in Conversion constructor vs. conversion operator: precedence), and how do I achieve the result without -pedantic in a standard-conforming manner?

$ g++ --version
g++.exe (Rev1, Built by MSYS2 project) 7.2.0
Tabb answered 2/5, 2018 at 14:32 Comment(4)
Adding non-const-qualified l-value or r-value references to the constructor's parameters solves it. I cannot explain it, though. Someone? The removed duplicate that explains the "Why" part.Dispassion
@Dispassion : std::ref returns an rvalue, so an lvalue reference would be ineligible and thus remove the ambiguity; I would think an rvalue reference would still be ambiguous, but maybe it's a better match during overload resolution (if so I'm not sure why).Roter
What about using direct initialization? Copy initialization is designed to take conversion functions into consideration.Buy
@Buy Both direct initialization and copy initialization through std::ref/std::cref should call the constructors taking a std::reference_wrapper.Tabb
P
1

how do I achieve the result without -pedantic in a standard-conforming manner?

Create overloads with exact match, in your case:

Class(std::reference_wrapper<Class>&&) {
    std::cout << "Class(std::reference_wrapper<Class>)" << std::endl;
}
Class(std::reference_wrapper<const Class>&&) {
    std::cout << "Class(std::reference_wrapper<const Class>)" << std::endl;
}

Demo

Pattani answered 2/5, 2018 at 16:32 Comment(3)
Yeah, this appears to solve the problem. Can you back up that this does indeed conform to the standard and isn't just another peculiarity of the compiler?Tabb
@Pattani Could you please explain why taking the parameters by value is not an exact match? This is why I withheld my answer.Dispassion
It only handles rvalue cases.Buy
S
1

Define the constructors as explicit to avoid the conflict with the conversion operator operator T& () const defined in the std::reference_wrapper class:

#include <functional>
#include <iostream>

class Class {
public:
    Class() {
        std::cout << "Class()" << std::endl;
    }
    Class(Class const &) {
        std::cout << "Class(Class const &)" << std::endl;
    }
    explicit Class(std::reference_wrapper<Class>) {
        std::cout << "Class(std::reference_wrapper<Class>)" << std::endl;
    }
    explicit Class(std::reference_wrapper<const Class>) {
        std::cout << "Class(std::reference_wrapper<const Class>)" << std::endl;
    }

};

int main() {
    Class a;
    Class b = a;
    Class c = std::ref(a);  // call Class(Class const &) trough the conversion operator
    Class d(std::ref(a));   // call Class(std::reference_wrapper<Class>)
    Class e = std::cref(a); // call Class(Class const &) trough the conversion operator
    Class f(std::cref(a));  // call Class(std::reference_wrapper<const Class>)
}

Alternatively remove the constructor overloads 3 and 4 to always use the copy constructor Class(Class const &).

You get no errors without the pedantic option because GCC gives precedence to copy constructor over conversion operators, but this is non part of the the standard, where no precedence is defined, hence the conflict.

Snide answered 2/5, 2018 at 16:46 Comment(3)
Unfortunately defining the constructors as explicit has the opposite effect to what I am trying to achieve. I want the constructors to have precedence over the conversion operator of std::reference_wrapper.Tabb
An explicit constructor is the way you can force the compiler to rule out any conversion operator, but yes, you have to explicitly tell the compiler when you really want to use the constructor as for d and f. Anyway, it never crossed my mind to provide a conversion constructor overload with a std::reference_wrapper argument, so now I'm curious way you want to do that... sorry for the ot question :)Snide
To satisfy curiosity... :) Class will internally store either a copy of the passed value or a reference to it (actually it will always store a pointer, but by value it will allocate a copy and own the pointer, by reference it will simply point to the passed value without owning it). std::ref/std::cref indicate that the value should be stored by reference, otherwise the value is stored by value.Tabb

© 2022 - 2024 — McMap. All rights reserved.