Boost Hana iterating over tuple of pairs [duplicate] - c++

This question already has an answer here:
hana::second can't deduce type
(1 answer)
Closed 5 years ago.
I have a template class which accepts a tuple of pairs of a type and an integral constant (some of the types can be repeated so it can't be a hana::map). I'm looking to iterate over the tuple and call a static toString() type method defined for each type. The error I am receiving is:
"error: type 'decltype(hana::first(c))' (aka 'boost::hana::type_impl::_ &') cannot be used prior to '::' because it has no members"
struct A
{
static std::string toString() {return std::string("A");}
};
struct B
{
static std::string toString() {return std::string("B");}
};
using namespace hana::literals;
std::array<std::string,3> ret;
constexpr auto tupleOfPairs = hana::make_tuple(
hana::make_pair(hana::type_c<A>, 0_c),
hana::make_pair(hana::type_c<B>, 0_c),
hana::make_pair(hana::type_c<B>, 5_c));
size_t idx = 0;
hana::for_each(tupleOfPairs, [&](auto c)
{
ret[idx++] = decltype(hana::first(c))::type::toString();
});
I had something very similar working when it was just a tuple (using decltype(c)::type::toString()) but as soon as I made the tuple elements pairs with an integral constant I can't seem to extract the type of the first element of the pair and do the same.
Thanks

TL;DNR: That decltype yields a type "reference to ...", which (it's a reference) obviously has no members. Simple fix: remove the reference with std::remove_reference:
ret[idx++] =
std::remove_reference_t<decltype(hana::first(c))>::type::toString();
This is not about hana. Taking it "out of the loop" we can reduce your case further to
#include <string>
struct A {
static std::string toString() {
return std::string("A");
}
};
template<typename T> T & get_it() {
static T thing;
return thing;
}
int main() {
std::string str = decltype(get_it<A>())::toString();
}
This gives (basically) the same error message as yours:
error: 'decltype(get_it())' (aka 'A &') is not a class, namespace, or enumeration
This is telling us that A & (a reference to A) is not a class (etc.) and can thus not have a (static) member. The same is true when your compiler tells you that:
error: type 'decltype(hana::first(c))' (aka 'boost::hana::type_impl::_ &') cannot be used prior to '::' because it has no members
boost::hana::type_impl::_ & is a reference. Of course it has no members. The referenced class has, so just remove the reference with std::remove_reference.
The fact that the return type of first is reference type is also noted in the documentation, btw (emphasis mine):
Returns the first element of a pair.Note that if the Product actually stores the elements it contains, hana::first is required to return a lvalue reference, a lvalue reference to const or a rvalue reference to the first element, where the type of reference must match that of the pair passed to first. If [.., not applicable here].

Related

Using constexpr vectors in template parameters (C++20)

Recently, I've been playing around with C++20's new constexpr std::vectors (I'm using GCC v12), and have run into a slight problem (this is actually an extension of my last question, but I thought it would just be better to make a new one). I've been trying to use constexpr std::vectors as members to a class, but this seems like it doesn't work as you cannot annotate them with constexpr and therefore constexpr functions think they can't be evaluated at compile time, so now I am trying to use template parameters instead, like this:
#include <array>
template<int N, std::array<int, N> arr = {1}>
class Test
{
public:
constexpr int doSomething()
{
constexpr const int value = arr[0];
return value * 100;
}
};
int main()
{
Test<10> myTestClass;
// return the value to prevent it from being optimized away
return myTestClass.doSomething();
}
This results in the expected assembly output (simply returning 100 from main):
main:
mov eax, 100
ret
However, something like this doesn't work for std::vectors, even though they can be constexpr now!
#include <vector>
template<std::vector<int> vec = {1}>
class Test
{
public:
constexpr int doSomething()
{
constexpr const int value = vec[0];
return value * 100;
}
};
int main()
{
Test<> myTestClass;
return myTestClass.doSomething();
}
This throws this error:
<source>:3:35: error: 'std::vector<int>' is not a valid type for a template non-type parameter because it is not structural
3 | template<std::vector<int> vec = {1}>
| ^
In file included from /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/vector:64,
from <source>:1:
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/stl_vector.h:423:11: note: base class 'std::_Vector_base<int, std::allocator<int> >' is not public
423 | class vector : protected _Vector_base<_Tp, _Alloc>
| ^~~~~~
<source>: In function 'int main()':
<source>:17:24: error: request for member 'doSomething' in 'myTestClass', which is of non-class type 'int'
17 | return myTestClass.doSomething();
How can I do this with vectors, or is it even possible? And, is it possible to make constexpr members, or not?
You still (in C++20 and I don't think there is any change for C++23) can't use a std::vector as a non-type template argument or have any std::vector variable marked constexpr or have any constant expression resulting in a std::vector as value at all.
The only use case that is allowed now in C++20 that wasn't allowed before is to have a (non-constexpr) std::vector variable or object which is constructed while a constant expression is evaluated and destroyed before the constant evaluation ends.
This means you can now for example take the function
int f() {
std::vector<int> vec;
vec.push_back(3);
vec.push_back(1);
vec.push_back(2);
std::sort(vec.begin(), vec.end());
return vec.front();
}
add a constexpr on it and use it in constant expression e.g.
static_assert(f() == 1);
But that's all. It is still very useful, because beforehand you could only use algorithms that don't need any dynamic memory allocation to calculate something at compile-time. That meant that you often couldn't just use the usual runtime algorithm/implementation directly in a compile-time context.
The same is true for any type that keeps references to dynamically allocated memory. You need to destroy them during the constant expression evaluation, i.e. they must be temporaries or local variables in a function or return values which are not stored in a runtime context.
In the specific case of non-type template arguments the situation is even stricter. Not all types that you could make a constexpr variable of, can be used as non-type template arguments. There are much stricter restrictions. They must be so-called structural types.
These are for example fundamental types such as arithmetic types, pointers, etc. A class type is a structural type if it is a literal type and also has only non-static data members which are public and non-mutable as well as all of them, recursively, structural types as well.
I think it is clear that std::vector doesn't satisfy these requirements. std::array is explicitly specified to be a structural type, which is why you are allowed to use it as non-type template argument.

Are these two types the same types of not? [duplicate]

This question already has answers here:
What expressions yield a reference type when decltype is applied to them?
(2 answers)
Closed 1 year ago.
I'm so confused about this template I wrote, It's supposed to 'auto' deduce a pointer type that I pass in, but the compiler doesn't seem to think that two types are the same, whereas I do, and typeid() is on my side.
#include <iostream>
template <auto* static_ptr_to_object>
struct Handle
{
// static_ptr_to_object should be a pointer to a Whale<char>
// Both of the following typedefs should result in types of Whale<char>
// When I cout typeid() of these types it prints out the same types
// However std::is_same_v returns false
using pointee_type1 = std::remove_pointer_t<decltype(static_ptr_to_object)>;
using pointee_type2 = decltype(*static_ptr_to_object); // The two are the same types
void showTypes()
{
//static_assert(std::is_same_v<pointee_type1, pointee_type2>);
// Uncommenting this line will make the static assert fail
std::cout << "Type of 'pointee_type1' = " << typeid(pointee_type1).name() << "\n";
std::cout << "Type of 'pointee_type2' = " << typeid(pointee_type2).name() << "\n";
if (typeid(pointee_type1) == typeid(pointee_type2))
std::cout << "Types are the same\n";
else std::cout << "Types are not the same\n";
}
bool b1 = std::is_integral_v<decltype(pointee_type1::member)>;
// Uncommenting the below line will make compilation fail
//bool b2 = std::is_integral_v<decltype(pointee_type2::member)>;
// pointee_type2 must be a class or namespace when followed by ::
// pointee_type2 left of '::' must be a class or namespace name
};
template <typename T>
struct Whale
{
T member;
};
Whale<char> whale;
int main()
{
Handle<&whale> handleToWhale;
handleToWhale.showTypes();
}
So I think the two types are the same, and typeid() operator says they are the same, yet std::is_same returns false, and based on the compiler error message it doesn't recognise that pointee_type2 is a class type because it doesn't recognise the :: operator for it.
Edit: My understanding about dereferencing pointers was wrong, this question relates to how a pointer dereference will return a reference type in some cases.
For decltype,
If the argument is an unparenthesized id-expression or an unparenthesized class member access expression, then decltype yields the type of the entity named by this expression. ......
If the argument is any other expression of type T, and
a) ...
b) if the value category of expression is lvalue, then decltype yields T&;
c) ...
*static_ptr_to_object fits the 2nd case, and it's an lvalue expression, so decltype(*static_ptr_to_object) yields a reference type, i.e. Whale<char>&, pointee_type1 and pointee_type2 are not the same type.
You can use std::remove_reference to get the same type as:
using pointee_type2 = std::remove_reference_t<decltype(*static_ptr_to_object)>;
On the other hand, typeid does yield same result (the same std::type_info object).
If type is a reference type, the result refers to a std::type_info object representing the referenced type.
pointee_type1 is Whale<char>, but pointee_type2 is Whale<char> &. typeid removes the reference, so you get the same name.

Convert enum class to lvalue reference to its underlying type in C++

I have the following code in C++11 (drastically simplified to leave out the inessentials):
#include <cstdint>
#include <type_traits>
enum class State : std::uint8_t
{
Shutdown,
Loading,
Active,
Idle,
Off
};
int main()
{
State state = State::Shutdown;
getBytes(state);
}
getBytes() is a library function which is used to deserialize data from stream:
void getBytes(std::uint8_t& buf) {
// in real codebase it would read from stream and modify buf
// here for simplicity I just modify the argument passed by reference
buf = 1;
}
The problem I face is that the scoped enum is not implicitly convertible to the uint8_t so I get a compiler error saying it. When I add the usual static_cast<std::underlying_type<State>::type>() I still get an error saying:
error: no matching function for call to 'getBytes'
getBytes(static_cast<uint8_t>(state));
^~~~~~~~
note: candidate function not viable: expects an l-value for 1st argument
void getBytes(std::uint8_t& buf) {
^
According to the answer here Is it safe to reinterpret_cast an enum class variable to a reference of the underlying type? it's not recommended to use reinterpret_cast in this situation.
I found a solution in the form of creating a temporary variable of underlying type, then passing it into the function and after that modifiying the enum value like this:
int main()
{
State state = State::Shutdown;
using UnderlyingType = std::underlying_type<State>::type;
UnderlyingType temp = static_cast<UnderlyingType>(state);
getBytes(temp);
state = static_cast<State>(temp);
}
However, since there are so many places in the codebase with this same problem I do not see it as a great solution. So my question is whether there is a way to call the function getBytes without creating this temporary?
As a side note, since getBytes is a library function I cannot modify it.
The issue is not the enum so much as it's the what happens with static_cast. getBytes can't insert a value into a static_cast value.
However, you could create two helper functions that do this for you
template <typename T>
constexpr auto to_integral(T e){
return static_cast<std::underlying_type_t<T>>(e);
}
State wrapped_getBytes(State in){
auto i = to_integral(in);
getBytes(i);
return State(i);
}

How to verify if returned `auto` variable is a reference [duplicate]

This question already has answers here:
C++ auto vs auto&
(2 answers)
Closed 5 years ago.
While doing code maintenance I found code like this:
auto networkEntry = _networkEntries[key];
networkEntry.port = port;
networkEntry.scope = scope;
The map data type used for _networkEntries has two overloaded versions of the operator[]:
template<class T>
class Map {
// ... simplified STD compatible container ...
T & Map::operator[](const Key & key);
const T Map::operator[](const Key & key) const;
};
The data type used in the map is a simple struct.
Now I just wondered, the returned value for auto could be a copy of the data structure, or a reference to the data structure. If a copy is returned, the assignments would not affect the stored values in the map.
I have three related question for this case:
Can I know or test which version of the operator[] was used?
Which C++ rules do apply here?
Is there a way, using auto, to make sure the reference is used?
auto networkEntry = _networkEntries[key];
Here networkEntry will never be a reference type, as auto type deduction rules follow template argument deduction rules.
In short, you need to either say:
auto& x = y;
Will compile only if y can be bound to an lvalue reference.
auto&& x = y;
Will always deduce a reference type, either lvalue/rvalue reference depending on the value category of y.
decltype(auto) x = y;
Will deduce exactly the type of y - it can deduce a reference or a value type. See What are some uses of decltype(auto)?.
As Yakk said, if y is not a reference type, x becomes an rvalue reference bound to the non-reference temporary y.
In order to deduce a reference.
If the implied this pointer at the calling site is const, then the const version of operator[] will be called, else the non-const version is called. So if the function containing your code is const, and _networkEntries is a member variable of that class, then the const version will be called.
As above.
If you want a reference, then use auto& not auto for the type at the calling site: auto& networkEntry = _networkEntries[key];.

Result of decltype in const methods

The C++11 decltype returns the type of the expression given to it (mostly). But this can differ from the type of the expression as it is actually accessible:
template<typename T>
struct Ref {
Ref(T&) { }
};
#define GETTYPE decltype
//#define GETTYPE typeof
struct Problem {
void doit_c() const { Ref<GETTYPE(n)> rn{n}; }
void doit_nc() { Ref<GETTYPE(n)> rn{n}; }
int n;
};
int main() {
int i;
const int ci = 0;
Problem pr;
// decltype == typeof == int
Ref<GETTYPE(i)> ri{i};
pr.doit_nc();
// decltype == typeof == const int
Ref<GETTYPE(ci)> rci{ci};
Ref<GETTYPE(static_cast<const int&>(i))> rcci{static_cast<const int&>(i)};
// typeof == const int, decltype == int (!)
pr.doit_c();
return 0;
}
In the example, the Ref struct is just used to cause a compile error if T does not match the actual constructor argument. The Problem::doit_c() method is where decltype(n) returns a non-const result, even though n is const in this context.
If one switches from the standard decltype to the GNU extension typeof, this seems to take the const-ness of the method into account.
Now my question: Is there a C++11 / C++14 / C++17 compliant alternative to decltype() / typeof() that behaves "correctly" (as in: no compile error above) for expressions like the declaration in the const-method above?
Edited:
Simplified the first sentence to remove some errors and stop distracting from the point of the question (thanks, #skypjack)
Simplified the use use of macros in the example code (thanks, #Richard Critten)
decltype is a feature that kinda sits at two chairs at once. Firstly, as the name suggests, it can give you the exact declared type of an entity, ignoring the context in which it is used. Secondly, it can treat its argument as an expression, whose exact type depends on the context and its value category.
decltype applied directly to a "naked" (unparenthesized) class member access is a special case, in which decltype acts in accordance with its first role. It will not treat n as an expression. Instead it will produce the type of that class member, ignoring the context.
If you want it to treat n as an expression, you have to parenthesize it
struct Problem {
void doit_c() const
{
Ref<decltype(n)> rn1{n}; // `decltype(n)` is `int` -> ERROR
Ref<decltype((n))> rn2{n}; // `decltype((n))` is `const int &` -> compiles OK
}
};
You can find out the effective cv-qualified type of n using a temporary reference:
void doit_c() const { auto& x = n; Ref<GETTYPE(x)> rn{n}; }
void doit_nc() { auto& x = n; Ref<GETTYPE(x)> rn{n}; }
But it's simpler and clearer to parenthesize, as shown in AnT's answer.