Suppose you have a set of pointers (yeah...) :
std::set<SomeType*> myTypeContainer;
Then suppose that you want to search this set from a const method of SomeType:
bool SomeType::IsContainered() const
{
return myTypeContainer.find(this) != myTypeContainer.end();
}
This doesn't work. The this ptr in the method is a const SomeType *const, which I can't put into the find. The issue being that find takes a const-ref, which in this case would mean that the passed pointer is treated as const, but not the thing it points to.
Is there a way to resolve this smoothly (without changing the set template type)?
In order to enable "mixed" comparison in an ordered container, you can use a key_compare type that declares the typename key_compare::is_transparent.
The default comparison functor class of set is std::less<Key>. It is not "transparent". But std::less<void> is "transparent" and performs the comparison of any arguments a and b as long as a<b is well formed. So you could define your own comparison functor type or you could use std::less<void> (or equivalently std::less<>):
set<SomeType*,std::less<>> myTypeContainer;
As you said, in the const member function this becomes const SomeType * (i.e. pointer to const), it can't be implicitly converted to SomeType * (i.e. pointer to non-const), which is the expected parameter type of find.
You could use const_cast to perform explicit conversion.
bool SomeType::IsContainered() const
{
return myTypeContainer.find(const_cast<SomeType *>(this)) != myTypeContainer.end();
}
It would be safe if the cast result is not used for modifying; while std::set::find won't do that.
Related
I am studying C++ templates, and I got stuck thinking about the interaction between const and types that are arguments to template functions. Specifically, I am thinking about how consts interact with template types when applied outside of the template parameter list.
I have tried looking for this interaction in C++ Primer 5th ed (Lippman) and in the C++11 standard draft, but const in this context is either not explicitly mentioned or (in the case of the standard) rather complex in its description (I'm still somewhat new to C++).
Here is a code example of my problem:
template<typename T>
const T & constify(T & t) {
return t;
}
...
int* i = 0x12345678;
constify(i);
I have two different expectations of the return type:
The deduced return type is const (int *) &, i.e the const is applied afterwards, so that we cannot modify the int pointer but we can modify what it points to.
The deduced return type is const int * &, i.e all declarators and qualifiers are applied all at once instead of as in 1. Here, we can no longer modify the int pointed to by the integer, but we can modify the pointer itself.
For me, the first one makes more sense because it has a natural "substitution-like" rule behind it, similar to typedef. But my question is; which of these (if any) is correct and why?
Template type substitutions are not textual, so do not think of them in terms of the textual type definition.
In your example, T is deduced to be int * - let's call it intptr. You are making const reference to it, so return value becomes const intptr&. That means, that the pointer itself can't be modified through this reference, but the value it points to can be modified.
Last, but not the least, you could have easily verifed your assumptions before asking the question :)
The 1st one is correct, and the return type would be int * const &, i.e. the reference to const pointer to non-const int; not const int * &, i.e. the reference to non-const pointer to const int.
const is qualified on T itself, when T is a pointer const T would be a const pointer but not a pointer to const pointee.
const consistent location is on the right of the type. Some declarations, like variables, also allow putting const on the left, but others, like member function definitions, only allow const on the right.
If you always put/apply const on the right it helps you to think about it in the right way. This is the reason many boost libraries put const on the right.
constify apples const on the right, hence, it turns T that is int* into int* const - a constant pointer to non-constant int. And apply the reference on the right of that: int* const&.
Having this set of objects and statements:
QSet<Foo*> set;
iterator QSet::insert(const T & value) //type of the function I want to call
const Foo * get() const //type of the function I use to get the argument
set.insert(get()); //the line showing up as error
I get the error "no known conversion for argument 1 from 'const Foo*' to 'Foo* const&". I guess I have trouble reading these types because I have no idea what I should do to make this work.
From what I've read, the const keyword applies to the type to its left with the exception of a top-level const which can be written to the left of the type it applies to. My guess would be that I have to convert get() to a reference but I'm unsure how to do that.
There seem to be a couple of misunderstandings here (both by the questioner and by some answers).
First, you said "My guess would be that I have to convert get() to a reference but I'm unsure how to do that". Let's try clearing this up:
1) "I have to convert get() to a reference" -- Actually, you don't!
iterator QSet::insert(const T & value) does indeed take a reference. But it's a reference to type T. So the question is, "what is type T"?
In this case, T=Foo *. So insert, in this instance of the template, is actually:
iterator QSet::insert(Foo * const & value) -- read it from right to left: insert takes a reference to a constant pointer to a Foo.
2) "I'm unsure how to do that [convert a pointer to a reference]" -- while you don't have to do it here, in general you do this by de-referencing the result of get. For example: *(get()).
Second, the compiler error. The error arises because there is a conflict:
a) get() returns a const Foo *
b) but set stores a Foo* -- NOT a const Foo *, so insert only accepts a changeable Foo
So you can't store a constant pointer inside your QSet<Foo*>. This makes sense because you can use set to access and change the Foos inside it, which you promise not to do with a const Foo.
This reference should be helpful:
https://isocpp.org/wiki/faq/const-correctness
You may also think whether you can just use a QSet<Foo> instead of a QSet<Foo*>. In the former case, things will probably behave how you expect.
You are trying to take a const Foo * and insert it into a QSet<Foo *>, but the compiler won't automatically convert a const Foo * to a plain Foo * in order to do so.
I solved the problem by changing the type of the set as follows.
QSet<Foo const*> set;
With this change, compilation succeeds.
You're violating the const-ness of the pointer returned from your get() function. get() returns a pointer to a const object of type Foo, but then you try to insert it into a vector of non-const Foo pointers. You need to const_cast the return value from get() or change the return type of get() from const Foo* to just Foo*.
I am still not quite sure about the "move semantics". In a simple point class,
class point
{
private:
double xval_, yval_, zval_;
public:
// Constructs
// ...
public:
// Access (return xval_)
inline const double x() const;
// Edit
inline double& x();
};
Should I use inline const double x() const; or inline double x() const; in my Access function?
It depends on what you want to do. If you want client code to be able to modify your private xval_ from outside the class, you want to return double&; however, you probably don't want to do that.
Your other two choices are to return a double or a const double. In this case, they're both the same; you don't get any benefit from adding a const in this case, so you should simply return double because the const is redundant.
double x() const;
If you had pointers or references, then you might want to consider specifying the const for the return type depending on what you wanted to do. For example:
const double* xPtr() const;
double* const xPtr() const;
const double* const xPtr() const;
Note I did not specify inline anywhere. That's a separate discussion that you didn't ask about in your original post. For this example, you can go either way--include it or omit it--the inline keyword has nothing to do with your original question.
I do not see a sense to return const double from the function,
There could be a sense to define the function as constexpr provided that you need to use its result at compile time.
In other cases the return type should be defined as simply double.
When returning by value, it's usually best not to return a const. Reason being: as long as your consumer doesn't change your state, you really don't care what it does with it.
Also, (excluding const cast) the client can always bind to a non-const by copying - but why would you want to force it to do that?
It's good to define const access functions (if you're able), because it allows a user of the class with a const reference to access the value (without having to copy away the const reference).
For the function above, the form:
inline const double x();
doesn't really make much sense when returning a non-pointer or reference type. Typically you reserve the const modifier when returning a pointer or a reference.
As I said in my comment, for built-in types, I don't think it matters. See Impact of returning const value types in C++11 on move semantics for why:
An expression calling a function that returns by value is a prvalue.
However, there are no const prvalues of non-class non-array type
(§5/6):
If a prvalue initially has the type “cv T,” where T is a
cv-unqualified non-class, non-array type, the type of the expression
is adjusted to T prior to any further analysis.
This means that there's no difference between your two definitions of
the function. Whether it returns a const int or just an int is
irrelevant because the expression is never const.
I would like to return a pointer to an array owned by a class. However, I do not want to allow users to modify that data or pointer. According to how I understand things you need return a constant pointer to constant data using the following syntax.
const Type *const Class::Method() {
return this->data_;
}
However gcc gives the following warning when compiling.
warning: type qualifiers ignored on function return type
Why is this warning provided by gcc? What does it mean? If this is not the right syntax for what I want, what is?
The warning you get is because the final const is overlooked. Take it off and you're set.
You don't need to return a const pointer to const data, just a pointer to const data (which is const Type*). Just as it doesn't make sense to return a const int, it doesn't make sense to return a T* const, because as soon as the value is assigned to a new variable, that const is discarded.
The top level const is ignored for built-in types. As there is a rule in C++[3.14p4]: class and array prvalues can have cv-qualified types; other prvalues always have cv-unqualified types.. In your case const Type* const, the top level const making the pointer const is ignored.
You could add const to the end: const Type * Class::Method() const {...}. This would prevent the pointer from being modified inside the member function. However, since the member function returns prvalue which is non-modifiable, it is not necessary to do this to prevent modification of the pointer member outside of the class (and this is also the reason why this rule exists in C++). It may be useful when you want to call the function with a constant reference to a Class object, etc., but for what you are doing, this doesn't seem necessary.
First const is right, the second const does not make any sense. Just look at this example:
const int foo();
int a = foo();
The fact if foo returns const int or just int does not change anything in this case. Same for Type *.
I'm using QVector to hold pointers to Objects, say FYPE*, in my program.
class TYPE {
// ....
};
const TYPE *ptrToCst;
QVector<TYPE*> qtVct;
// ...
if (qtVct.contains(ptrToCst)) { // Error!!!
// ....
}
The compiler says QVector::contains expects TYPE* instead of const TYPE* as its parameter. A const_cast operation would solve this problem. But it doesn't make any sense to me, since the contains method should never change what the pointer points to. And the equivalent code using STL vector works as expected.
std::vector<TYPE*> stlVct;
// ...
if (std::find(stlVct.begin(), stlVct.end(), ptrToCst)) { // Ok
// ...
}
What's the reason for this difference? Did STL treat containers that hold pointers specially, so that std::find accept pointer to const object? I guess partial template specialization was involved?
This is actually a quite interesting question about const-correctness and some non-intuitive errors, but the compiler is right in rejecting the code.
Why does is not work in Qt?
The compiler rejects the code because it would break const-correctness of the code. I don't know the library, but I think I can safely assume that the signature of contains is QVector<T>::contains( T const & value )[1], which in the case of T == type* means:
QVector<type *>::contains( type * const & value )
Now the problem is that you are trying to pass a variable of type type const *. The problem at this point is that the compiler does not know what contains does internally, only the promises it offers in it's interface. contains promises not to change the pointer, but says nothing about the pointee. There is nothing in the signature inhibiting the implementation of contains from modifying the value. Consider this similar example:
void f( int * const & p ) { // I promise not to change p
*p = 5; // ... but I can modify *p
}
int main() {
const int k = 10;
int const * p = &k;
f( p ); // Same problem as above: if allowed, f can modify k!!!
}
Why does it allow the similar call to std::find then?
The difference with std::find is that find is a template where the different argument type have a very loose coupling. The standard library does not perform type checking on the interface, but rather on the implementation of the template. If the argument is not of the proper type, it will be picked up while instantiating the template.
What this means is that the implementation will be something similar to:
template <typename Iterator, typename T>
Iterator find( Iterator start, Iterator end, T const & value ) {
while ( start != end && *start != value )
++start;
return start;
}
The type of the iterators and the value are completely unrelated, there is no constraint there other than those imposed by the code inside the template. That means that the call will match the arguments as Iterator == std::vector<type*>::iterator and T = type const *, and the signature will match. Because internally value is only used to compare with *start, and the comparison of type * and type const * const is valid (it will convert the former to the later and then compare [2]) the code compiles perfectly.
If the template had the extra restriction that the type of the second argument was Iterator::value_type const & (this could be implemented with SFINAE) then find would fail to compile with the same error.
[1] Note the choice of ordering in the declaration: type const *, rather than const type *. They are exactly the same for the compiler (and the experienced eye), but by always adding the const to the right it makes it trivial to identify what is being defined as const, and to identify that const T & with T == type * in the argument of contains is not const type *&.
[2] In the same way that you cannot bind a type * const & to a type const *, you cannot do the equivalent with pointers, type * const * cannot be converted from type const *, but if const is added to both the pointer and the pointee types, then the conversion is fine as it guarantees not to break const correctness. That conversion (which is safe) is performed in the comparison of type * == type const * const, where the left hand side gets two extra const: type const * const. If it is not clear why this does not break const-correctness, drop a comment and I will provide some code here.
Explicit template instantiation is done for concrete types, and you can be certain standard library vendor had no idea you'd write TYPE. That aside, the difference is in signatures. std::find is a free function template, something like:
template <typename I, typename T>
find(I first, I last, T value)
So, when you call it, compiler generates find(std::vector<TYPE*>::iterator, std::vector<TYPE*>::iterator, const TYPE*). Since all find does is comparisons, and you can compare const T* and T* with no problems, all is good and fluffy.
QVector<TYPE*>::contains, on the other hand, is a member function, in a class template. So it signature contains the type used to instantiate the template:
contains(TYPE*)
And therein lies the problem, because you try to call it with const TYPE* — and conversion of const T* to T* is illegal.
Also: find returns an iterator, not a boolean. Your condition should be if (std::find(...) != that_vector.end()).
To directly answer "Why QVector::contains expects pointer to non-const TYPE as its parameter": because you told it to, with the template argument.