I would like to ask a question about structural binding and how the variables get their types.
Here is the code I compile.
struct T {
explicit T(int a = 1) {
a_ = a;
std::cout << "Default" << std::endl;
}
T(const T& t) {
a_ = t.a_;
std::cout << "Copy" << std::endl;
}
int a_;
};
int main() {
std::tuple<bool, T> bi{true, T(1)};
std::cout << "START" << std::endl;
auto& [b, i] = bi;
static_assert(std::is_same<decltype(i), T>::value);
std::cout << i.a_ << std::endl;
i.a_++;
std::cout << i.a_ << std::endl;
std::cout << (&i == &get<1>(bi)) << std::endl;
return 0;
}
The code compiles and the result is:
Default
Copy
START
1
2
1
As you can see the variable i actually refers to get<1>(bi).
However, the type of the variable i is T since std::is_same<decltype(i), T>::value.
Could you please explain why structural binding works this way?
I have read about it in the book "C++ Templates: The Complete Guide"
and the authors state that the variable i must have type std::tuple_element<1, std::tuple<bool, T> >::type&&. I don't understand why decltype(i) is just T.
decltype is funny that way. Its specification contains a special clause for structured bindings
4 For an expression e, the type denoted by decltype(e) is
defined as follows:
if e is an unparenthesized id-expression naming a structured binding ([dcl.struct.bind]), decltype(e) is the referenced type as
given in the specification of the structured binding declaration;
...
So while each structured binding is indeed a reference into the tuple, decltype reports the type of the referent. Likely since structured bindings are meant to feel like regular named variables.
Related
I'm debugging an issue in a large C++ codebase where an attribute of a struct is occasionally being changed to a bad value. Unfortunately, the attribute is public and is accessed or changed in hundreds of places, so simply adding a breakpoint on a mutator is not possible. Also, I don't know the instance of the struct, so adding an address watchpoint wouldn't help.
Instrumenting the code would be a major job. However, a colleague helpfully suggested creating a proxy class which could wrap the existing type in the struct declaration. For example, instead of using MyType _type I would replace this with ChangeProxy<MyType> _type in the struct and the application should compile and work with the proxy taking the place of the direct type in the same manner as, for example, a smart pointer.
However, when I build an example, the implicit conversion operation in the template class doesn't appear to get invoked in type deduction. Here's the code:
#include <iostream>
class MyType {
long _n = 0;
public:
MyType() {}
MyType(const long n) : _n{n} {}
MyType& operator=(const long n) { _n = n; return *this; }
bool isZero() const { return _n != 0; }
};
template <class T>
class ChangeProxy {
public:
ChangeProxy() {}
ChangeProxy(const T& t) : _t{t} {}
ChangeProxy(const T&& t) : _t{std::move(t)} {}
ChangeProxy& operator=(const T& t) {onChange(t); _t = t; return *this;}
ChangeProxy& operator=(const T&& t) {onChange(t); _t = std::move(t); return *this;}
operator T() {return _t;}
private:
T _t;
void onChange(const T& newVal) { /* something here to notify me of changes */ };
};
struct MyStruct {
// MyType _type; // this works ...
ChangeProxy<MyType> _type; // .. but this doesn't
};
int main() {
MyStruct i;
std::cout << "i._type.isZero() : " << std::boolalpha << i._type.isZero() << std::endl;
i._type = 1;
std::cout << "i._type.isZero() : " << std::boolalpha << i._type.isZero() << std::endl;
return 0;
}
Unfortunately, when I build this I get the following errors:
proxy-variable~test.cpp:35:73: error: ‘class ChangeProxy<MyType>’ has no member named ‘isZero’
35 | std::cout << "i._type.isZero() : " << std::boolalpha << i._type.isZero() << std::endl;
| ^~~~~~
proxy-variable~test.cpp:37:73: error: ‘class ChangeProxy<MyType>’ has no member named ‘isZero’
37 | std::cout << "i._type.isZero() : " << std::boolalpha << i._type.isZero() << std::endl;
| ^~~~~~
So it seems that the compiler isn't deducing that it can cast a ChangeProxy<MyType> to a MyType. What have I done wrong here?
The context here doesn't let the compiler try out implicit conversions. Calling a member function on some object never does. You can force this by e.g.
std::cout << "i._type.isZero() : " << std::boolalpha <<
static_cast<MyType>(i._type).isZero() << '\n';
// ^^^^^^^^^^^^^^^^^^^ Here, enforce conversion
Another option would be:
MyStruct i;
const MyType& underlying = i._type; // Again, request conversion manually
std::cout << underlying.isZero() << '\n';
What you are doing is invoking a method on the class ChangeProxy<MyType> which indeed doesn't have any method isZero() defined on it, hence the compilation error. You could probably add something like
T const& operator()() const {return _t;}
And then call it using
i._type().isZero()
The reason that the wrapped i._type.isZero() can never work is that implicit conversions of the i._type object aren't considered for direct method calls, and you can't overload operator. like you can operator->.
It's nothing to do with type deduction, there's simply no mechanism in the language to do what you want.
Luckily, you're solving the wrong problem anyway.
... a colleague helpfully suggested creating a proxy class which could wrap the existing type in the struct declaration
Hmm, you didn't mention that here - or am I a colleague now?
an attribute of a struct is occasionally being changed to a bad value
Which attribute? Be specific!
In your code, you're treating the MyType instance as the problematic attribute. However, the only state in MyType is its long _n member.
Writing
class MyType {
ChangeProxy<long> _n = 0;
which is what I actually suggested when I referred to wrapping built-in types, avoids this problem entirely. You may of course need operator!= to make isZero work, but that's a normally overloadable operator.
Oddly the code in your question doesn't permit any mutation of _n anyway, so it's unclear how it can be getting a bad value. However, I assume this is just an artefact of a simplified example.
This question already has an answer here:
How to properly use concepts?
(1 answer)
Closed last year.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I'm trying to write a concept that instantiates a function if the template parameter is only of numeric type (integers, floats, doubles and so on) and throw an error if the type is a pointer type or a boolean type.
#include <iostream>
#include <type_traits>
#include <boost/type_index.hpp>
template<typename T>
concept NumericType = requires(T param)
{
{std::is_integral_v<T> || std::is_floating_point_v<T>};
{!std::is_same_v<bool, T>};
{std::is_arithmetic_v<decltype(param +1)>};
!std::is_pointer_v<T>;
};
But when I try to use it with pointer to int or float or double, the compiler instantiates the function and adds one to the pointer which works because of pointer arithmetic.
template<NumericType T>
T add_one(const T val)
{
return val + 1;
}
int main(){
int two{2};
int* pTwo = &two;
float three{3.1415};
std::cout << "add_one(two): " << add_one(two) << "\n";
std::cout << "add_one(&two): " << add_one(&two) << "\n";
std::cout << "add_one(pTwo): " << add_one(pTwo) << "\n";
std::cout << "add_one(&three): " << add_one(&three) << "\n";
std::cout << "add_one(true): " << add_one(true) << "\n";
return 0;
}
I was expecting the compiler to throw error for the cases where pointer or boolean types passed to add_one function, but surprisingly it doesn't
I fail to understand what what is wrong with the definition of concept NumericType since it contains a clause within the body of the concept definition that hints the compiler that type T should not be of pointer or boolean type using the type trait std::pointer_v<T> `!std::is_same_v<T, bool>. Here's a working example on compiler explorer.
The requires clause only checks the validity of the expression and does not evaluate the value, you need to use nested requires:
template<typename T>
concept NumericType = requires(T param)
{
requires std::is_integral_v<T> || std::is_floating_point_v<T>;
requires !std::is_same_v<bool, T>;
requires std::is_arithmetic_v<decltype(param +1)>;
requires !std::is_pointer_v<T>;
};
I am not sure I understand why the first test evaluates to true and the second to false. I know that the information from typeid().name() is usually not reliable, but my main problem is with the typeid itself. I don't understand why the type of *test is not Location<1>, or what else is wrong. Any thoughts? Is there same wrapper around a type here that I don't see? Thanks in advance, and apologies if the answer is obvious.
#include <iostream>
#include <utility>
#include <typeinfo>
class LocationAbstract
{
virtual void get_() = 0;
};
template<int i>
class Location : public LocationAbstract
{
public:
static constexpr int test = i;
virtual void get_() override
{
return;
}
};
template <int i>
Location<i> LocationGenerator()
{
Location<i> test{};
return test;
}
int main()
{
LocationAbstract *table[10];
table[0] = new decltype(LocationGenerator<0>());
table[1] = new decltype(LocationGenerator<1>());
Location<1> *test;
try
{
std::cout << "Casting\n";
test = dynamic_cast<Location<1>*>(table[1]);
}
catch (std::bad_cast &e)
{
std::cout << "Bad cast\n";
}
// test1, evaluates to true
std::cout << (typeid(*test) == typeid(*dynamic_cast<Location<1>*>(table[1]))) << "\n";
std::cout << typeid(*test).name() << "\n";
std::cout << typeid(*dynamic_cast<Location<1>*>(table[1])).name() << "\n----\n";
// test2, why does this evaluate to false while the above evaluates to true ?
std::cout << (typeid(Location<1>()) == typeid(*dynamic_cast<Location<1>*>(table[1]))) << "\n";
std::cout << typeid((Location<1>())).name() << "\n";
std::cout << typeid(*dynamic_cast<Location<1>*>(table[1])).name() << "\n";
auto test1 = Location<1>();
auto test2 = *dynamic_cast<Location<1>*>(table[1]);
std::cout << typeid(test1).name() << " and " << typeid(test2).name() << "\n";
return 0;
}
An extra set of () makes all the difference here. In typeid(Location<1>()) and typeid((Location<1>())), Location<1>() actually means two totally different things.
In typeid(Location<1>()), Location<1>() is interpreted as a function type that returns a Location<1> and takes no parameters.
In typeid((Location<1>())), Location<1>() is interpreted as value-initializing an anonymous Location<1> object.
The typeid operator can work on either types or expressions. That is, you can say typeid(int) as well as typeid(42). Since Location<1>() can be interpreted as a type, the language does so. (Location<1>()) cannot be interpreted as a type though, so it must be interpreted as an expression. The only thing Location<1>() can mean as part of an expression is to value-initialize an anonymous Location<1> object, so typeid gives you the type of that object.
Let this be yet another reason to prefer uniform-initialization syntax when creating temporary objects; Location<1>{} would not have this ambiguity.
Examine these two lines:
std::cout << (typeid(Location<1>()) == typeid(*dynamic_cast<Location<1>*>(table[1]))) << "\n";
std::cout << typeid((Location<1>())).name() << "\n";
In the first line, you use typeid(Location<1>()). typeid can take types as well as expressions, and Location<1>() is a function type with no parameters and a return type of Location<1>.
So why does the name print the same? That's because of the second line: typeid((Location<1>())). By wrapping the argument in parentheses, it is no longer a valid type, so it is treated as an expression and the name of typeid(Location<1>) is printed. Removing the extra parentheses prints F8LocationILi1EEvE under the same mangling scheme.
To avoid the ambiguity, you can also use the type directly (typeid(Location<1>)) or use braces: typeid(Location<1>{})).
#include <iostream>
using namespace std;
int main()
{
cout << typeid(int).name() << endl;
cout << typeid(int&).name() << endl;
cout << typeid(int&&).name() << endl;
cout << typeid(const int).name() << endl;
cout << typeid(const int&).name() << endl;
cout << typeid(const int&&).name() << endl;
}
I think the output should be:
int
int&
int&&
const int
const int&
const int&&
However, the real output is:
int
int
int
int
int
int
Both of clang & vc++ do the same way.
Is there a reliable way to check the exact type name (with cv-ref-pointer traits) in C++?
Note that when you pass a reference type to typeid operator, the result std::type_info object represents the referenced type. And cv-qualifiers will be ignored.
1) Refers to a std::type_info object representing the type type. If type is a reference type, the result refers to a std::type_info object representing the referenced type
In all cases, cv-qualifiers are ignored by typeid (that is, typeid(const T) == typeid(T))
And note that what std::type_info::name returns is implementation-defined.
You can apply the trick from Effective Modern C++ (Scott Meyers) Item 4: Know how to view deduced types, to get the type name at compile-time. e.g.
template <typename>
struct TD;
then use it as
TD<int> td;
you'll get some message mentioning the type name, like
error: implicit instantiation of undefined template 'TD<int>'
LIVE (clang)
As I understand it, both decltype and auto will attempt to figure out what the type of something is.
If we define:
int foo () {
return 34;
}
Then both declarations are legal:
auto x = foo();
cout << x << endl;
decltype(foo()) y = 13;
cout << y << endl;
Could you please tell me what the main difference between decltype and auto is?
decltype gives the declared type of the expression that is passed to it. auto does the same thing as template type deduction. So, for example, if you have a function that returns a reference, auto will still be a value (you need auto& to get a reference), but decltype will be exactly the type of the return value.
#include <iostream>
int global{};
int& foo()
{
return global;
}
int main()
{
decltype(foo()) a = foo(); //a is an `int&`
auto b = foo(); //b is an `int`
b = 2;
std::cout << "a: " << a << '\n'; //prints "a: 0"
std::cout << "b: " << b << '\n'; //prints "b: 2"
std::cout << "---\n";
decltype(foo()) c = foo(); //c is an `int&`
c = 10;
std::cout << "a: " << a << '\n'; //prints "a: 10"
std::cout << "b: " << b << '\n'; //prints "b: 2"
std::cout << "c: " << c << '\n'; //prints "c: 10"
}
Also see David Rodríguez's answer about the places in which only one of auto or decltype are possible.
auto (in the context where it infers a type) is limited to defining the type of a variable for which there is an initializer. decltype is a broader construct that, at the cost of extra information, will infer the type of an expression.
In the cases where auto can be used, it is more concise than decltype, as you don't need to provide the expression from which the type will be inferred.
auto x = foo(); // more concise than `decltype(foo()) x`
std::vector<decltype(foo())> v{ foo() }; // cannot use `auto`
The keyword auto is also used in a completely unrelated context, when using trailing return types for functions:
auto foo() -> int;
There auto is only a leader so that the compiler knows that this is a declaration with a trailing return type. While the example above can be trivially converted to old style, in generic programming it is useful:
template <typename T, typename U>
auto sum( T t, U u ) -> decltype(t+u)
Note that in this case, auto cannot be used to define the return type.
That's my thinking about the auto and decltype:
The most obvious difference in pratice between the two is:
In type deduction for expr, decltype deduce the correct type( except the lvalue expr -> lvalue ref) and auto default to value.
We need to learn the "data stream" model before understanding the difference.
In our codes, the function calling can be resolved as a data stream model(something like the concept of functional program), so the function which is been called is the data receiver, and the caller is the data provider. It is obviously that the data type must be decided by the data receiver, or the data cannot be organized in order in the data stream.
Look this:
template<typename T>
void foo(T t){
// do something.
}
the T will be deduced to a value type, regardless of whether you pass.
If you want the ref type, you should use auto& or auto&&, that's what I am saying, the data type is decided by the data receiver.
Let's return to the auto:
auto is used to do a type deduction for rvalue expr, giving the data receiver a correct interface to receive the data.
auto a = some expr; // a is data receiver, and the expr is the provider.
So why dose auto ignore the ref modifier?
because it should be decided by the receiver.
Why we need decltype?
The answer is : auto cannot to be used as a true type deduction, it will not give you the correct type of a expr. It just give the data receiver a correct type to receive the data.
So, we need decltype to get the correct type.
modify #Mankarse's example code,I think a better one blew:
#include <iostream>
int global = 0;
int& foo()
{
return global;
}
int main()
{
decltype(foo()) a = foo(); //a is an `int&`
auto b = foo(); //b is an `int`
b = 2;
std::cout << "a: " << a << '\n'; //prints "a: 0"
std::cout << "b: " << b << '\n'; //prints "b: 2"
std::cout << "global: " << global << '\n'; //prints "global: 0"
std::cout << "---\n";
//a is an `int&`
a = 10;
std::cout << "a: " << a << '\n'; //prints "a: 10"
std::cout << "b: " << b << '\n'; //prints "b: 2"
std::cout << "global: " << global << '\n'; //prints "global: 10"
return 0;
}
I consider auto to be a purely simplifying feature whereas the primary purpose of decltype is
to enable sophisticated metaprogramming in foundation libraries. They are however very closely
related when looked at from a language-technical point of use.
From HOPL20 4.2.1, Bjarne Stroustrup.
Generally, if you need a type for a variable you are going to initialize, use auto. decltype is better used when you need the type for something that is not a variable, like a return type.