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.
Related
Maybe the title is not clear, so concretely:
#include <type_traits>
template<typename T>
constexpr int test(T)
{
return std::is_integral<T>::value;
}
int main()
{
constexpr int a = test(1); // right
constexpr int b = test(1.0); // right
int c = 2;
constexpr int d = test(c); // ERROR!
return 0;
}
In fact, the function doesn't use anything but the type of the parameter, which can be determined obviously in the compilation time. So why is that forbidden and is there any way to make constexpr get the value when only the type of parameter is used?
In fact, I hope to let users call the function through parameters directly rather than code like test<decltype(b)>, which is a feasible but not-convenient-to-use way, to check if the types of parameters obey some rules.
Just take T by reference so it doesn't need to read the value:
template<typename T>
constexpr int test(T&&)
{
return std::is_integral<std::remove_cvref_t<T>>::value;
}
You can even declare test with consteval, if you want to.
(Note that stripping cv-qualifiers isn't necessary in this instance; cv-qualified integral types satisfy the std::is_integral trait.)
Why can't constexpr be used for non-const variables when the function only uses the types?
Because the call expression test(c) is not a constant expression and hence it cannot be used as an initializer for d.
Basically, for the call expression test(c) to be a constant expression, c must be a constant expression. It doesn't matter whether c is used or not inside the function itself. This is why, the call expressions test(1) and test(1.0) works and can be used as an initializer for a and b respectively.
#include <type_traits>
template<typename T, typename... Args_>
concept IsCallable = std::is_invocable_v<T, Args_...>;
struct A
{
int n = 0;
void f(IsCallable<int&> auto const fn)
{
fn(n);
}
void f(IsCallable<int const&> auto const fn) const
{
fn(n);
}
};
struct Lambda
{
void operator()(auto& n) const
{
n = 1;
}
};
int main()
{
auto a = A{};
a.f(Lambda{}); // ok
a.f([](auto& n) { n = 1; }); // error: assignment of read-only reference 'n'
}
See online demo
Why does C++ lambda overloading not behave as expected?
Your question is nearly a duplicate of Hard error when using std::invoke_result_t with a generic lambda . As with the other question, your code will work the way you expect, if you change the lambda so that it has an explicit -> void return type, thus avoiding return type deduction.
The difference between your question and the other one is that in your question, you're using std::invocable_v rather than std::invoke_result_t. However, the code in this case is still ill-formed because return type deduction is still triggered even though you're not asking for it.
The standard requires is_invocable to determine whether or not the INVOKE expression would be "well-formed when treated as an unevaluated operand". libstdc++ and libc++ both use decltype to create an unevaluated operand for this purpose, so, obviously, return type deduction has to be done. But it seems to me that there is no way to avoid triggering return type deduction, therefore there is no possible standard library implementation that would allow your code to compile (it is not a bug in GCC/Clang).
Is there any case, where we must use trailing return type, because the problem cannot be phrased in the old way?
auto fn() -> int; can be easily transformed to the old way: int fn();.
I wonder, is there an example, where this transformation is not possible. The most straighforward example, when we refer to function parameters in the return type, seems to be solvable by using declval.
Note: don't consider lambdas here, where we must use trailing return type.
In a trailing return type, you're allowed to apply decltype to this (see this question).
With the old syntax, you'd have to spell the class name manually... which you can't do if the class is unnamed!
(Or if the member function is generated with a macro, so the class name isn't known.)
struct
{
auto foo() -> decltype(this)
{
return this;
}
/*
decltype(this) foo() // error: invalid use of 'this' at top level
{
return this;
}
*/
} x;
I admit that this is a slightly unrealistic example, and you can easily work around it by naming the class, but I couldn't think of anything else.
One bizzare example I can think of, which needs some prerequisites.
Consider a function which cannot use auto return type deduction (e.g. it has multiple return values which cannot be deduced to the same type) and uses generic function from C++ concepts. Then you don't have a type to use for std::declval and auto deduction won't work:
auto foo(auto x)
// -> decltype(x) // comment this out to fix
{
if(x > 0) return x;
return -1; // requires int to be implicite castable to type of x
}
Demo
In C++14, why do lambda functions with a deduced return type drop references from the return type by default? IIUC, since C++14 lambda functions with a deduced return type (without an explicit trailing return type) have a return type of auto, which drops references (among other things).
Why was this decision made? It seems to me like a gotcha to remove a reference when that's what your return statement returns.
This behavior caused the following nasty bug for me:
class Int {
public:
Int(int i) : m_int{i} {}
int m_int;
};
class C {
public:
C(Int obj) : m_obj{obj} {}
const auto& getObj() { return m_obj; }
Int m_obj;
};
class D {
public:
D(std::function<const Int&()> f) : m_f{f} {}
std::function<const Int&()> m_f;
};
Int myint{5};
C c{myint};
D d{ [&c](){ return c.getObj(); } } // The deduced return type of the lambda is Int (with no reference)
const Int& myref = d.m_f(); // Instead of referencing myint, myref is a dangling reference; d.m_f() returned a copy of myint, which is subsequently destroyed.
Specifying the desired return type when initializing d resolves the issue:
D d{ [&c]() -> const Int& { return c.getObj(); } }
Interestingly, even if the auto return type deduction makes sense, isn't it a bug that std::function<const Int&> gets happily initialized with a function that returns a non-reference? I see this also by writing explicitly:
D d{ [&c]() -> Int { return c.getObj(); } }
which compiles without a problem. (on Xcode 8, clang 8.0.0)
I think the place you are stumbling is actually with the expression c.getObj() in the line return c.getObj();.
You think the expression c.getObj() has type const Int&. However that is not true; expressions never have reference type. As noted by Kerrek SB in comments, we sometimes talk about expressions as if they had reference type, as a shortcut to save on verbosity, but that leads to misconceptions so I think it is important to understand what is really going on.
The use of a reference type in a declaration (including as a return type as in getObj's declaration) affects how the thing being declared is initialized, but once it is initialized, there is no longer any evidence that it was originally a reference.
Here is a simpler example:
int a; int &b = a; // 1
versus
int b; int &a = b; // 2
These two codes are exactly identical (except for the result of decltype(a) or decltype(b) which is a bit of a hack to the system). In both cases the expressions a and b both have type int and value category "lvalue" and denote the same object. It's not the case that a is the "real object" and b is some sort of disguised pointer to a. They are both on equal footing. It's one object with two names.
Going back to your code now: the expression c.getObj() has exactly the same behaviour as c.m_obj, apart from access rights. The type is Int and the value category is "lvalue". The & in the return type of getObj() only dictates that this is an lvalue and it will also designate an object that already existed (approximately speaking).
So the deduced return type from return c.getObj(); is the same as it would be for return c.m_obj; , which -- to be compatible with template type deduction, as mentioned elsewhere -- is not a reference type.
NB. If you understood this post you will also understand why I don't like the pedagogy of "references" being taught as "disguised pointers that auto dereference", which is somewhere between wrong and dangerous.
The standard (at least, the working draft) already gives you hints about what's happening and how to solve it:
The lambda return type is auto, which is replaced by the type specified by the trailing-return-type if provided and/or deduced from return statements as described in [dcl.spec.auto]. [ Example:
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return { 1, 2 }; }; // error: deducing return type from braced-init-list int j;
auto x3 = []()->auto&& { return j; }; // OK: return type is int&
— end example ]
Consider now the following template function:
template<typename T>
void f(T t) {}
// ....
int x = 42;
f(x);
What's t in f, a copy of x or a reference to it?
What happens if we change the function as it follows?
template<typename T>
void f(T &t) {}
The same applies more or less to the deduced return type of a lambda: if you want a reference, you must be explicit about that.
Why was this decision made? It seems to me like a gotcha to remove a reference when that's what your return statement returns.
The choice is consistent with how templates work since the beginning.
I would be surprised of the opposite instead.
Return type is deduced as well as the template parameter and it's a pretty good decision not to define different set of rules for them (at least from my point of view).
That said, to solve your problem you have several alternatives:
[&c]()->auto&&{ return c.getObj(); }
[&c]()->auto&{ return c.getObj(); }
[&c]()->decltype(c.getObj())&{ return c.getObj(); }
[&c]()->decltype(c.getObj())&&{ return c.getObj(); }
[&c]()->decltype(auto){ return c.getObj(); }
[&c]()->const Int &{ return c.getObj(); }
...
Some of them are crazy, some of them are quite clear, all of them should work.
If the intended behavior is to return a reference, probably to be explicit about that is the best choice:
[&c]()->auto&{ return c.getObj(); }
Anyway, this is mostly opinion-based, so feel free to pick your preferred alternative up and use it.
Interestingly, even if the auto return type deduction makes sense, isn't it a bug that std::function gets happily initialized with a function that returns a non-reference?
Let's consider the code below (no reason to call in a std::function right now):
int f() { return 0; }
const int & g() { return f(); }
int main() { const int &x = g(); }
It gives you a few warnings but it compiles.
The reason is that a temporary is created from an rvalue and a temporary can bind to a const reference, so I'd say it's legal from the point of view of the standard.
The fact that it will explode at runtime is another problem.
Something similar happens when using a std:: function.
Anyway it's an abstraction over a generic callable object, so do not expect the same warnings.
I tried to find a solution for the problem of the question C++ template non-type parameter type deduction, which does not involve a template parameter to call f, but implicitly chooses the correct type for the template parameter.
Since constexpr should guarantee that a function only contains compile time constants, and is evaluated at compile time (at least thats what i think it does), i thought it might be the solution for this issue.
So i came up with this:
template <class T, T VALUE> void f() {}
//first i tried this:
template <class T> auto get_f(T t) -> decltype( &f<T,t> ) { return f<T,t>; }
//second try:
template <class T> constexpr void (&get_f( T t ))() { return f<T,t>; }
int main()
{
get_f(10)(); //gets correct f and calls it
}
first version generates following error:
error: use of parameter 't' outside function body
which is really confusing, since the usage of parameters in the decltype statement of a trailing return type should be ok?
second version generates following error:
error: invalid initialization of non-const reference of type 'void (&)()'
from an rvalue of type '<unresolved overloaded function type>'
which is kinda confusing, since i fully qualified f in get_f.
I would expect this kind of error messages if i did not have the constexpr. So do i have a false understanding of what constexpr does, or is the C++0x implementation of GCC flawed for this case ?
I am using GCC 4.6.2
Since constexpr should guarantee that a function only contains compile
time constants, and is evaluated at compile time (at least thats what
i think it does), i thought it might be the solution for this issue.
A constexpr function can be used in a constant expression context, but is not restricted to one. In this respect they are different from a metafunction and a regular function. Consider the problem of returning the successor of an integer:
// Regular function
int f(int i)
{ return i + 1; }
// Regular metafunction
template<int I>
struct g {
static constexpr auto value = I + 1;
};
// constexpr function
constexpr int h(int i)
{ return i + 1; }
// Then...
{
// runtime context: the metafunction can't be used
int i;
std::cin >> i;
f(i); // Okay
g<i>::value; // Invalid
h(i); // Okay
// compile time context: the regular function can't be used
char a[f(42)]; // Invalid
char b[g<42>::value]; // Okay
char c[h(42)]; // Okay
}
constexpr has other usages (e.g. constructors) but when it comes to constexpr functions this is the gist of it: some functions should be available in both runtime and constant contexts because some computations are available in both. It's possible to compute i + 1 whether i is a compile-time constant or is extracted from std::cin.
This means that inside the body of a constexpr function the parameters are not themselves constant expressions. So what you are attempting is not possible. Your function can't deal with
int i;
std::cin >> i;
get_f(i); // what's the return type?
and the violation happens here:
constexpr auto get_f(T t)
-> decltype( &f<T,t> ) // <-
Since t is not a constant expression according to the rules of the language (no matter what, even if you actually only pass constant expressions in), it can't appear as the second template parameter of f.
(And in the larger picture it means that no, you can't use argument deduction from function templates to conveniently pass a non-type parameter to a class template.)