Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

libc++ is_copy_constructible seems wrong to me

The libc++ implementation of is_copy_constructible is like so:

template <class _Tp>
struct _LIBCPP_TYPE_VIS_ONLY is_copy_constructible
    : public is_constructible<_Tp, const typename add_lvalue_reference<_Tp>::type>
    {};

The C++ specification for is_copy_constructible is simply:

std::is_copy_constructible specification: std::is_constructible<T, const T&>::value is true.

However, isn't the implementation above implementing T& const instead of const T&? Applying const to add_lvalue_reference should have no effect, and at least one compiler (EDG) recognizes this in the form of a warning.

Example program demonstrating the problem:

#include <type_traits>

struct ProofTest
{
    ProofTest(){}
    ProofTest(const ProofTest&) = delete;  // is_copy_constructible should use this.
    ProofTest(ProofTest&){ }               // But instead it's using this.
};

void Proof()
{
    static_assert(std::is_copy_constructible<ProofTest>::value == false, "is_copy_constructible bug");
}

Under libstdc++ the above code compiles OK, but under libc++ the static_assert fires.

Is the following the correct fix?:

template <class _Tp>
struct _LIBCPP_TYPE_VIS_ONLY is_copy_constructible
    : public is_constructible<_Tp, typename add_lvalue_reference<typename std::add_const<_Tp>::type>::type>
    {};

This affects a couple other libc++ type traits as well.

like image 600
ThreeBit Avatar asked Nov 19 '13 01:11

ThreeBit


1 Answers

Agreed, thanks for the bug report.

Update

Related question: What's the expected value of: std::is_constructible<int&>::value? It's not perfectly clear to me from reading the standard.

What the standard says:

For a referenceable type T, the same result as is_constructible<T, const T&>::value, otherwise false.

A "referenceable type" is basically anything but a void. I'm paraphrasing. This is not an exact definition. It is meant to be understandable as opposed to precise. A language lawyer (including myself) can tear it apart. But for ease in understanding, "anything but a void" is close enough.

So your question becomes, what is:

std::is_constructible<int&, const (int&)&>::value  // I've used pseudo code

const applied to references is a no-op (). And lvalue references applied to lvalue references is a no-op (due to reference collapsing). For example consider this non-portable type_name facility:

#include <type_traits>
#include <memory>
#include <iostream>
#include <cxxabi.h>
#include <cstdlib>

template <typename T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

int
main()
{
    typedef int& T;
    std::cout << type_name<const T&>() << '\n';
}

For me this prints out:

int&

So the above simplifies to:

std::is_constructible<int&, int&>::value  // true

which should answer true since an lvalue int should be constructible from a non-const lvalue int.

like image 87
Howard Hinnant Avatar answered Oct 30 '22 09:10

Howard Hinnant