Data member pointers as associative container keys - c++

I am trying to create an std::set of pointers to data members. However, I can't find a method to sort or hash such pointers.
They can't be compared with operator<, they don't seem to be supported by std::less and there is no standard integer type that is guaranteed to hold their representation (they might not fit in std::uintptr_t).
This is what I tried first (https://godbolt.org/z/K8ajn3rM8) :
#include <set>
struct foo
{
int x;
int y;
};
using t_member_ptr = int (foo::*);
const std::set<t_member_ptr> members = {
&foo::x,
&foo::y
};
It produces the error error: invalid operands of types 'int foo::* const' and 'int foo::* const' to binary 'operator<'. The full error message also implies this occurs during instantiation of std::less.
I found a similar question (Set of pointer to member) but it dates back to C++14 and the answers boil down to "put your pointers in a vector and perform a linear search instead".
Has there been any changes with C++17 or C++20 that make it possible to use pointers to data members as keys for standard associative containers?

Compare them bytewise, e.g. using this comparator:
#include <cstring>
#include <type_traits>
struct BitLess
{
template <typename T>
requires std::has_unique_object_representations_v<T>
constexpr bool operator()(const T &a, const T &b) const
{
return std::memcmp(reinterpret_cast<const char *>(&a), reinterpret_cast<const char *>(&b), sizeof(T)) < 0;
}
};
Checking std::has_unique_object_representations_v<T> ensures that there's no padding inside. It tried it on GCC, Clang, and MSVC, and it returned true for member pointers on all three.

Related

more than one operator "[]" matches these operands

I have a class that has both implicit conversion operator() to intrinsic types and the ability to access by a string index operator[] that is used for a settings store. It compiles and works very well in unit tests on gcc 6.3 & MSVC however the class causes some ambiguity warnings on intellisense and clang which is not acceptable for use.
Super slimmed down version:
https://onlinegdb.com/rJ-q7svG8
#include <memory>
#include <unordered_map>
#include <string>
struct Setting
{
int data; // this in reality is a Variant of intrinsic types + std::string
std::unordered_map<std::string, std::shared_ptr<Setting>> children;
template<typename T>
operator T()
{
return data;
}
template<typename T>
Setting & operator=(T val)
{
data = val;
return *this;
}
Setting & operator[](const std::string key)
{
if(children.count(key))
return *(children[key]);
else
{
children[key] = std::shared_ptr<Setting>(new Setting());
return *(children[key]);
}
}
};
Usage:
Setting data;
data["TestNode"] = 4;
data["TestNode"]["SubValue"] = 55;
int x = data["TestNode"];
int y = data["TestNode"]["SubValue"];
std::cout << x <<std::endl;
std::cout << y;
output:
4
55
Error message is as follows:
more than one operator "[]" matches these operands:
built-in operator "integer[pointer-to-object]" function
"Setting::operator[](std::string key)"
operand types are: Setting [ const char [15] ]
I understand why the error/warning exists as it's from the ability to reverse the indexer on an array with the array itself (which by itself is extremely bizarre syntax but makes logical sense with pointer arithmetic).
char* a = "asdf";
char b = a[5];
char c = 5[a];
b == c
I am not sure how to avoid the error message it's presenting while keeping with what I want to accomplish. (implicit assignment & index by string)
Is that possible?
Note: I cannot use C++ features above 11.
The issue is the user-defined implicit conversion function template.
template<typename T>
operator T()
{
return data;
}
When the compiler considers the expression data["TestNode"], some implicit conversions need to take place. The compiler has two options:
Convert the const char [9] to a const std::string and call Setting &Setting::operator[](const std::string)
Convert the Setting to an int and call const char *operator[](int, const char *)
Both options involve an implicit conversion so the compiler can't decide which one is better. The compiler says that the call is ambiguous.
There a few ways to get around this.
Option 1
Eliminate the implicit conversion from const char [9] to std::string. You can do this by making Setting::operator[] a template that accepts a reference to an array of characters (a reference to a string literal).
template <size_t Size>
Setting &operator[](const char (&key)[Size]);
Option 2
Eliminate the implicit conversion from Setting to int. You can do this by marking the user-defined conversion as explicit.
template <typename T>
explicit operator T() const;
This will require you to update the calling code to use direct initialization instead of copy initialization.
int x{data["TestNode"]};
Option 3
Eliminate the implicit conversion from Setting to int. Another way to do this is by removing the user-defined conversion entirely and using a function.
template <typename T>
T get() const;
Obviously, this will also require you to update the calling code.
int x = data["TestNode"].get<int>();
Some other notes
Some things I noticed about the code is that you didn't mark the user-defined conversion as const. If a member function does not modify the object, you should mark it as const to be able to use that function on a constant object. So put const after the parameter list:
template<typename T>
operator T() const {
return data;
}
Another thing I noticed was this:
std::shared_ptr<Setting>(new Setting())
Here you're mentioning Setting twice and doing two memory allocations when you could be doing one. It is preferable for code cleanliness and performance to do this instead:
std::make_shared<Setting>()
One more thing, I don't know enough about your design to make this decision myself but do you really need to use std::shared_ptr? I don't remember the last time I used std::shared_ptr as std::unique_ptr is much more efficient and seems to be enough in most situations. And really, do you need a pointer at all? Is there any reason for using std::shared_ptr<Setting> or std::unique_ptr<Setting> over Setting? Just something to think about.

Defining custom map comparator with non stardard signature

Assuming that there is a map that has a pointer as a key. To deep compare of the underling object it's possible to write custom comparator in following way:
#include <map>
#include <memory>
bool compare(std::unique_ptr<int> lhs, std::unique_ptr<int> rhs){
return *lhs<*rhs;
}
int main(){
std::map<std::unique_ptr<int>, short, bool (*)(std::unique_ptr<int>, std::unique_ptr<int>)> elements(compare);
}
That's undestandable, but why would I need to explicitly pass type of the compare callback as it is imposed by the type of map key? Why it's not possible create a map in following way as the signature of it should be known:
std::map<std::unique_ptr<int>, short> elements(compare);
Are there any cases when it's needed to define a compare function with different signature?
EDIT:
The last question I think I already answered to myself. At some point I could write something like:
#include <map>
#include <memory>
bool compare(std::unique_ptr<BaseClass> lhs, std::unique_ptr<BaseClass> rhs){
return *lhs<*rhs;
}
int main(){
std::map<std::unique_ptr<DerivedClass>, short, bool (*)(std::unique_ptr<BaseClass>, std::unique_ptr<BaseClass>)> elements(compare);
}
But it does not explain why by default exact signature is not used.
Why it's not possible create a map in following way as the signature of it should be known
But it's not known uniquely. The function is a valid initializer for at least two types of maps
std::map<std::unique_ptr<int>, short, bool (*)(std::unique_ptr<int>, std::unique_ptr<int>)>
std::map<std::unique_ptr<int>, short, bool (&)(std::unique_ptr<int>, std::unique_ptr<int>)>
A pointer or a reference to a function works equally well here. Neither is inherently better, so how's the type to be determined uniquely? There's no immediately compelling reason to favor one over the other.
It's also worth mentioning that the slightly more idiomatic use of a std::map is backwards. I.e. instead of figuring out the type from a comparator object, a user-defined type that can initialize objects is used instead. For example:
struct compare {
bool operator()(std::unique_ptr<int> lhs, std::unique_ptr<int> rhs){
return *lhs<*rhs;
}
};
std::map<std::unique_ptr<int>, short, compare> elements;
The type of the comparator is not at all determined by the type of the key. The comparator doesn't have to be a function, it could be a class overloading operator().
In fact, the third template parameter has a default argument that is an instantiation of just such a class, namely std::less<Key>. That is, std::map<std::unique_ptr<int>, short> is a shorthand for std::map<std::unique_ptr<int>, short, std::less<std::unique_ptr<int>>>

vector<bool> raises an error on const data() method

I have the following code:
#include <vector>
struct TestStruct {
std::vector<float> float_vect;
std::vector<bool> bool_vect;
};
void func(const TestStruct & test)
{
const float * p1 = test.float_vect.data(); //<--- this line works fine
const bool * p2 = test.bool_vect.data(); //<--- on this line error happens
}
int main()
{
TestStruct test;
func(test);
}
Error message:
passing 'const std::vector' as 'this' argument of 'void std::vector::data() [with _Alloc = std::allocator]' discards qualifiers [-fpermissive]
data() method of std::vector have const specified.
Why this method works fine on float vector, and raises an error on boolean vector ?
vector<bool> is a specialization of a good old vector<T> and it may be implemented differently from ordinary vector (e.g. some space-saving optimizations may be employed). Side-effect of such design is that it does not always behave as ordinary vector (many consider vector<bool> to be broken because of that).
For example, the reference at http://en.cppreference.com/w/cpp/container/vector_bool does not mention vector<bool>::data() at all. Therefore - you should not use it when using vector with type bool. The fact that you don't get an error similar to method not found is - in your case - just a matter of how vector<bool> is implemented by your compiler.
std::vector<bool> is a template specialization of the class std::vector<T> in the STL. In order to use less memory, the boolean values are stored by 8 in a byte. It means there is no direct data accessible as you expect because the class doesn't store it in the same way than for other types.
Look at the doc :
The specialization has the same member functions as the unspecialized vector, except data, emplace, and emplace_back, that are not present in this specialization.

What do member types mean in vectors?

When I went through std::vector over at cppreference.com and found a section called "Member types" I did not understand what that means. In fact the member types section is present in all the reference documents of containers in stl library.
Can someone help me in understanding this?
The member types define the types that are used by the vector object. Many standard containers use member types to describe the types used, so that the programmer does not need to figure them out manually. This is particularly useful when working with complex templates, where types may be hard to determine.
For example, the return type of std::vector<___>::size() is usually std::size_t, however another C++ vector implementation may return a different integer type (e.g. int32_t). Instead of assuming what types you need in your code (and possibly introducing dangerous casting), you can simply use the member types exposed by the vector to write your code with the perfect types every time. For example:
std::vector<int> vec;
const std::vector<int>::value_type val = 4;
// std::vector<int>::value_type is a typedef of int!
vec.push_back(val);
const std::vector<int>::size_type size = vec.size();
// std::vector<int>::size_type is a typedef of std::size_t, usually.
const std::size_t size2 = vec.size();
// same as above, but we assume that vec.size() returns a size_t.
// If it does not, we may cause a narrowing conversion!
for (std::vector<int>::const_iterator it = vec.begin(), end = vec.end(); it != end; ++it)
{
// The const_iterator type is also a member type of vector.
std::cout << *it << std::endl;
}
Iterators are probably the most common usage of member types in standard containers. Rather than having to figure out if an iterator is a random access iterator, or a simple forward iterator, or any other kind of iterator, we can just use the iterator member type exposed by the container.
The C++11 auto keyword can take this even further. Instead of doing:
const std::vector<int>::size_type size = vec.size();
we can now just do:
const auto size = vec.size();
and the compiler will automatically work it out.
Generally speaking most C++ standard objects will use the same member types where possible (e.g. size_t for size_type, T for value_type, T& for reference_type), but it is not guaranteed (iterator member type is different for std::vector and std::list, because their implementations are wildly different and they cannot use the same iterator type).
Here's a typical class in C++:
struct Foo
{
int n; // non-static data member
static const float x; // static data member
int f() const; // non-static member function
static int g(); // static member function
template <typename T>
void h(T); // non-static member function template
struct X { bool a; }; // member type
template <typename> struct Q; // member class template
using T = Q<int>; // member type (member typedef)
};
Example usage:
// Use static members
print(Foo::x);
print(Foo::g());
Foo::X y;
Foo::Q<double> z;
Foo::T w;
// Use non-static members (requires instance)
void demo(const Foo & a)
{
print(a.n);
print(a.f());
print(a.h<float>());
}
The page you linked lists a "member type" of "value_type T". This is type definition that belongs to or is a member of T. Consider
using VEC = std::vector<double>;
VEC dv { 4.2 };
Now lets say we want to store a value from dv but we don't want to hard-code the type so that a future change to the definition of VEC will Do The Right Thing (TM).
using VEC = std::vector<double>;
VEC dv { 4.2 };
// ...
VEC::value_type d = dv.front();
Or if you want to write cross-platform portable code, it can be helpful to use 'size_type' to ensure you store sizes that are large enough:
int s = dv.size(); // wrong on 64-bit system
VEC::size_type s = dv.size(); // always correct
That is to say, std::vector<double> hosts a type definition, value_type, which evaluates to the type of 'T' in vector, or in our case double.
This is mostly important for generic programming:
template<typename T>
void dothing(const T& container)
{
T::const_pointer* c = &container; // for some reason I need it's address
// .. other stuff
}
There are two things you need to understand:
You can make aliases for types.
For example,
using blah = int;
would make blah an alias for int. In other words, you would be able to use blah instead of int in any place.
(There is another way to make a type alias using typedef instead of using, but it is less readable.)
You can put those aliases inside of classes.
For example, if you declared a following struct
struct S
{
using type = int;
};
you would be able to use S::type instead of an int.
Such aliases inside of classes are often called member types.

boost::tuple with member function pointer

For some reason I get this error message
invalid operands of types 'void (S::* const)()' and 'void (S::* const)()' to binary 'operator<'
for this code snippet:
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>
struct S
{
void f() {}
};
typedef void(S::*tdef)();
int main()
{
boost::tuple<tdef> t1(&S::f);
boost::tuple<tdef> t2(&S::f);
return t1 < t2;
}
Boost documents are very tight-lipped on using member function pointers in tuples (apart from they are valid elements), so I don't really have a clue what could be the problem or how those 'const' qualifiers got into the expression.
Any hint?
Tuples will try to do the comparison on a function pointer and you can only compare function pointers for equality. Please also refer to this question
Function pointers are not relationally comparable in C++. Equality comparisons are supported, except for situations when at least one of the pointers actually points to a virtual member function (in which case the result is unspecified).