Is Casting Std::Pair<T1, T2> Const& to Std::Pair<T1 Const, T2> Const& Safe

Is casting std::pairT1, T2 const& to std::pairT1 const, T2 const& safe?

It's NOT portable to do so.

std::pair requirements are laid out in clause 20.3. Clause 17.5.2.3 clarifies that

Clauses 18 through 30 and Annex D do not specify the representation of classes, and intentionally omit specification of class members. An implementation may define static or non-static class members, or both, as needed to implement the semantics of the member functions specified in Clauses 18 through 30 and Annex D.

This implies that it's legal (although incredibly unlikely) for an implementation to include a partial specialization such as:

template<typename T1, typename T2>
struct pair<T1, T2>
{
T1 first;
T2 second;
};

template<typename T1, typename T2>
struct pair<const T1, T2>
{
T2 second;
const T1 first;
};

which are clearly not layout-compatible. Other variations including inclusion of additional non-static data members possibly before first and/or second are also allowed under the rule.


Now, it is somewhat interesting to consider the case where the layout is known. Although Potatoswatter pointed out DR1334 which asserts that T and const T are not layout-compatible, the Standard provides enough guarantees to allow us to get most of the way anyway:

template<typename T1, typename T2>
struct mypair<T1, T2>
{
T1 first;
T2 second;
};

mypair<int, double> pair1;
mypair<int, double>* p1 = &pair1;
int* p2 = reinterpret_cast<int*>(p1); // legal by 9.2p20
const int* p3 = p2;
mypair<const int, double>* p4 = reinterpret_cast<mypair<const int, double>*>(p3); // again 9.2p20

However this doesn't work on std::pair as we can't apply 9.2p20 without knowing that first is actually the initial member, which is not specified.

Const Conversion in std::pair

What you are trying to do is only possible if curr->valPair is not needed anymore after the conversion, then

std::pair<const string, int> newPair(std::move(curr->valPair));

moves the std::pair<string, int> through move construction into a std::pair<const string, int>.

If the value of curr->valPair must be retained there is no way of doing this because type conversion requires construction of an instance of the destination type. Copying the string can only be avoided through a move.

However you probably want to do this conversion because you need the specific destination type with const string somewhere. It would be better if you explained the intended usage. The problem likely lies there.

After question edit:

You cannot have a reference of type std::pair<const int, int>& pointing to an object of type std::pair<int, int>. These are two completely independent types. You need to construct an object of type std::pair<const int, int> first before you can have a reference to such a type.

Additional note:

reinterpret_cast may be valid here (and would result in exceptions to my statements above), I am not sure, here is a discussion on the topic. See @StoryTeller's answer.

Is this a defect in C++ that std::getT ( const std::pairconst T, U& ) fail to compile due to const T?

Is this a defect in C++ that std::get<T>(const std::pair<const T, U>& ) fail to compile due to const T?

No. I would very much expect this to fail:

std::pair<const int, bool> p(42, true);
std::get<int>(p); // expected failure

std::get<T> means to retrieve the element whose type is T. Not the element whose type approximates T, or decays to T, or any other such. std::get comes to pair by way of tuple, where it is specified as:

Requires: The type T occurs exactly once in Types.... Otherwise, the program is ill-formed.

If we consider pair as a special case of tuple, int does not occur exactly once in {const int, bool}, so the program should be ill-formed.

Put in other words: you are asking for the int in that pair, but there is no int there. There's a const int and there's a bool.

Cast of std::vector of same parameter type but with different constant qualifier

Ignoring that you said std::vector for the moment and pretending you had some other less well defined vector implementation. Your code would be technically unsafe not because T and T const are quite different but because the C++ language permits vector<T> and vector<T const> to be specialised in ways that are quite different. Consider the following code:

#include <iostream>

template <class T>
struct vector {
T* start_;
T* end_;
T* cap_end_;
};

template <class T>
struct vector<T const> {
bool gotcha_;
T* start_;
T* end_;
T* cap_end_;
};

struct foo { };

int
main()
{
std::cout
<< sizeof(vector<foo>) << '\n'
<< sizeof(vector<foo const>) << '\n'
;
}

Note that there are other more pernicious changes that could make your life miserable. Such as the following where the members are reordered:

#include <iostream>

template <class T>
struct vector {
T* start_;
T* end_;
T* cap_end_;
};

template <class T>
struct vector<T const> {
T* end_;
T* cap_end_;
T* start_;
};

template <class T>
long size(vector<T> const& v)
{
return v.end_ - v.start_;
}

struct foo { };

int
main()
{
vector<foo> v;
v.start_ = new foo[10];
v.end_ = v.start_ + 1;
v.cap_end_ = v.start_ + 10;

std::cout
<< size(v) << '\n'
<< size(*reinterpret_cast<vector<foo const>*>(&v)) << '\n'
;

return 0;
}

Wrt to std::vector, I am not familiar enough with the fine details of the standard library specification to know whether such specialisations would be conformant or not. Perhaps someone more well versed in the standard can comment.

Note some of what I said in answer to Casting templated class to more general specialization may help explain this problem.

To address your question about detecting specialisations there are ways to make your code unsafe by using no specialisations of the class but overloaded non-member functions and I am o not sure how you would detect that. Such as in the following:

#include <iostream>

template <class T>
struct vector {
T* start_;
T* end_;
T* cap_end_;
};

template <class T>
void init(vector<T>& v, size_t sz, size_t cap)
{
v.start_ = new T[cap];
v.end_ = v.start_ + sz;
v.cap_end_ = v.start_ + cap;
}

template <class T>
void init(vector<T const>& v, size_t sz, size_t cap)
{
v.end_ = new T const[cap];
v.cap_end_ = v.end_ + sz;
v.start_ = v.end_ + cap;
}

template <class T>
long size(vector<T>& v)
{
return v.end_ - v.start_;
}

template <class T>
long size(vector<T const>& v)
{
return v.cap_end_ - v.end_;
}

struct foo { };

int
main()
{
vector<foo const> v;
init(v, 1, 10);

std::cout
<< size(v) << '\n'
<< size(*reinterpret_cast<vector<foo>*>(&v)) << '\n'
;
}

Enough with the bad news. The good news is that if you want to take an existing object with a general interface and restrict or adjust what can be done with that object there is are some simple, safe and comprehensible ways of doing that. Take a look at std::stack http://www.sgi.com/tech/stl/stack.html or alternatively this answer https://stackoverflow.com/a/994925/453436 to What is Proxy Class in C++

cast void* to classes with multiple inheritance

The problem with your code is that your shared pointers are null, and therefore behaviour of indirecting through them is undefined.

You can create shared objects using std::make_shared.

Keep in mind however, that the shared objects are destroyed as soon as last shared pointer is destroyed, at which point any pointers to the objects in your map (if any) are left dangling.

How is it possible that std::shared_ptrDerived casts to std::shared_ptrBase with no compiler errors?

std::shared_ptr has a constructor (from cppreference):

template< class Y >
shared_ptr( const shared_ptr<Y>& r ) noexcept; (9)

This overload ...

Constructs a shared_ptr which shares ownership of the object managed
by r. If r manages no object, this manages no object too. The
template overload doesn't participate in overload resolution if Y
is
not implicitly convertible to (until C++17)compatible with (since
C++17) T*.

Hence, in some sense the tricky part is not to convert the shared pointer, but to prevent it when the pointer types are not implicitly convertible. You can use SFINAE to achieve that.

Here is a toy example that has conversions from Bar<T1> to Bar<T2> (but not the other way around) enabled only when T1 inherits from T2:

#include <type_traits>

template <typename T1>
struct Bar {
Bar() {}

template <typename T2, typename std::enable_if_t<std::is_base_of_v<T1,T2>,int> = 0>
Bar(Bar<T2>){}

};

struct Foo {};
struct Derived : Foo {};

int main(){
Bar<Derived> d;
Bar<Foo> b;
//d = b; // eror
b = d; // OK
}

Live Demo

You probably want it more general, like shared pointer, to allow such conversion whenever a T2* can be converted to a T1*, not only when they inherit from each other (see std::is_convertible, I have to admit, I don't really understand the change that came with C++17, so I can only guess: maybe its std::is_layout_compatible in that case). So to mimic a pre-C++17 smart pointer you could use:

    template <typename T2, typename std::enable_if_t<std::is_convertible_v<T2*,T1*>,int> = 0>
Bar(Bar<T2>){}

which enables the conversion for all T2 where T2* can be converted to a T1*.

Why isn't it legal to convert pointer to pointer to non-const to a pointer to pointer to const

From the standard:

const char c = 'c';
char* pc;
const char** pcc = &pc; // not allowed
*pcc = &c;
*pc = 'C'; // would allow to modify a const object


Related Topics



Leave a reply



Submit