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 toconst 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 inTypes...
. 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
Why Is the Copy Constructor Called When We Pass an Object as an Argument by Value to a Method
Inconsistent Strcmp() Return Value When Passing Strings as Pointers or as Literals
Effective Use of C++ Iomanip Library
Reading JSON File with C++ and Boost
Why Do Two Functions Have the Same Address
Is It More Efficient to Copy a Vector by Reserving and Copying, or by Creating and Swapping
Class Members and Explicit Stack/Heap Allocation
Does Clearing a Vector Affect Its Capacity
How to Load & Call a Vbscript Function from Within C++
How to Set the Cout Locale to Insert Commas as Thousands Separators
Sorting Std::Strings with Numbers in Them
Are the "Usual Arithmetic Conversions" and the "Integer Promotions" the Same Thing
Why Can't I Use a "Break" Statement Inside a Ternary Conditional Statement in C++