Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it permissible for standard library implementation to have class definition that is different from the C++ standard?

The following code successfully compiled with clang and MSVC but fail to compile in GCC 6.1.0.

#include <memory>

template<typename R, typename T, typename... Args>
T* test(R(T::*)(Args...) const)
{
    return nullptr;
}

int main()
{
    using T = std::shared_ptr<int>;
    T* p = test(&T::get);
}

with the following error message

prog.cc: In function 'int main()':
prog.cc:13:16: error: invalid conversion from 'std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2u>*' to 'T* {aka std::shared_ptr<int>*}' [-fpermissive]
     T* p = test(&T::get);
            ~~~~^~~~~~~~~

The problem is that libstdc++ implemented std::shared_ptr by inheriting member function get from a base class std::__shared_ptr.

In the C++ standard 20.8.2.2 Class template shared_ptr, it specifies the class definition of std::shared_ptr class with all the member functions of that class.

My question is whether the implementation must at least provide all public class members as defined in the standard inside the standard class? Is it allowed to provide the member functions by inheriting from base class as implemented in libstdc++?

like image 231
kwanti Avatar asked Jun 24 '16 16:06

kwanti


1 Answers

The standard's specification for types and their members is normative text, unless it explicitly says otherwise. As such, an implementation is required to follow that... to the extent that an implementation is required to follow anything from the standard.

And that extent is the "as if" rule. Namely, the implementation is allowed to do what it wants so long as the type behaves "as if" it were done as specified. The reason the standard has specific language stating that types can be derived from arbitrary, implementation-provided base classes is because that is something which a user can detect. It's visible behavior (through implicit conversions and the like) and therefore the standard would have to make an exception in order to allow for it.

Inheriting a member is almost the same thing as declaring it in your main class. Indeed, the only way I know of to tell the difference is to do what you did here: use template argument deduction rules. Even though you can specify the member as Derived::get, if it really comes from some base class, the compiler will know.

However, [member.functions] comes to GCC's rescue here. It has explicit language allowing standard library implementations to add additional overloads to a class. Because of that, your use of std::shared_ptr<int>::get here is not well-defined behavior. Indeed, footnote 187 clarifies this:

Hence, the address of a member function of a class in the C++ standard library has an unspecified type.

That's merely a footnote, but the intent seems clear: you cannot rely on any particular implementation to return any particular type of member pointer. Even if you applied a cast operation to the right signature, there is no guarantee that it would work.

So while the class definition in the standard library is normative text, [member.functions] makes it clear that the only thing you can guarantee about those definitions is that you can call those functions using the arguments provided. Anything else, like getting member pointers, is implementation-defined.

like image 139
Nicol Bolas Avatar answered Nov 08 '22 23:11

Nicol Bolas