Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous constructor taking std::reference_wrapper when compiling with -pedantic

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
like image 206
zennehoy Avatar asked May 02 '18 14:05

zennehoy


2 Answers

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

like image 63
Jarod42 Avatar answered Nov 15 '22 00:11

Jarod42


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.

like image 23
Gabriella Giordano Avatar answered Nov 14 '22 23:11

Gabriella Giordano