Edit, in order to avoid confusion: decltype does not accept two arguments. See answers.
The following two structs can be used to check for the existance of a member function on a type T during compile-time:
// Non-templated helper struct:
struct _test_has_foo {
template<class T>
static auto test(T* p) -> decltype(p->foo(), std::true_type());
template<class>
static auto test(...) -> std::false_type;
};
// Templated actual struct:
template<class T>
struct has_foo : decltype(_test_has_foo::test<T>(0))
{};
I think the idea is to use SFINAE when checking for the existance of a member function, so in case p->foo() isn't valid, only the ellipses version of test, which returns the std::false_type is defined. Otherwise the first method is defined for T* and will return std::true_type. The actual "switch" happens in the second class, which inherits from the type returned by test. This seems clever and "lightweight" compared to different approaches with is_same and stuff like that.
The decltype with two arguments first looked surprising to me, as I thought it just gets the type of an expression. When I saw the code above, I thought it's something like "try to compile the expressions and always return the type of the second. Fail if the expressions fail to compile" (so hide this specialization; SFINAE).
But:
Then I thought I could use this method to write any "is valid expression" checker, as long as it depends on some type T. Example:
...
template<class T>
static auto test(T* p) -> decltype(bar(*p), std::true_type());
...
http://ideone.com/dJkLPF
This, so I thought, will return a std::true_type if and only if bar is defined accepting a T as the first parameter (or if T is convertible, etc...), i.e.: if bar(*p) would compile if it was written in some context where p is defined of type T*.
However, the modification above evaluates always to std::false_type. Why is this? I don't want to fix it with some complicated different code. I just want to know why it doesn't work as I expected it to. Clearly, decltype with two arguments works different than I thought. I couldn't find any documentation; it's only explained with one expression everywhere.
It's an comma-separated list of expressions, the type is identical to the type of the last expression in the list. It's usually used to verify that the first expression is valid (compilable, think SFINAE), the second is used to specify that decltype should return in case the first expression is valid.
decltype does not take two arguments. Simply, it can can have an expression as its argument, and the comma operator is one way of creating expressions. Per Paragraph 5.18/1:
[...] A pair of expressions separated by a comma is evaluated left-to-right; the left expression is a discarded-value
expression (Clause 5). Every value computation and side effect associated with the left expression
is sequenced before every value computation and side effect associated with the right expression. The type
and value of the result are the type and value of the right operand; the result is of the same value category
as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field. If the value of the right
operand is a temporary (12.2), the result is that temporary.
Therefore:
static_assert(std::is_same<decltype(42, 3.14), double>::value, "Will not fire");
Related
To make a concept checking if a type can be converted without narrowing to another, it is proposed here to make it using std::forward and std::type_identity_t like this:
template<class T, class U>
concept __construct_without_narrowing = requires (U&& x) {
{ std::type_identity_t<T[]>{std::forward<U>(x)} } -> T[1];
};
I understand from it why something like this:
To{std::declval<From>()}
gives incorrect results, but when i try to simplify it using another idea in the paper, writing just
template <typename From, typename To>
concept WithoutNarrowing =
requires (From x) {
{(To[1]){x}}
->std::same_as<To[1]>;
};
It seems to give the same results. What circumstances have to occur for it to give different result? Or is it equivalent? For what reason is std::forward used here?
This is the usual approach for type traits like this that involve some kind of function/constructor argument.
U is the type from which T is supposed to be constructed, but if we want to discuss the construction we also need to consider the value category of the argument. It may be an lvalue or a rvalue and this can affect e.g. which constructor is usable.
The idea is that we map the rvalue argument case to a non-reference U or rvalue reference U and the lvalue argument case to a lvalue reference U, matching the mapping of expressions in decltype and of return types with value categories in function call expressions.
Then, by the reference collapsing rules, U&& will be a lvalue reference if the constructor argument is a lvalue and otherwise a rvalue reference. Then using std::forward means that the actual argument we give to the construction will indeed be a lvalue argument when U was meant to represent one and a rvalue argument otherwise.
Your approach using {(To[1]){x}} doesn't use the forwarding and so would always only test whether construction from a lvalue can be done without narrowing, which is not what is expected if e.g. U is a non-reference.
Your approach is further incorrect because (To[1]){x} is not valid syntax in standard C++. If X is a type you can have X{x} or (X)x, but not (X){x}. The last syntax is part of C however and called a compound literal there. For that reason a C++ compiler may support it as an extension to C++. That's why the original implementation uses the round-about way with std::type_identity_t.
The implementation seems to also be written for an earlier draft of C++20 concepts. It is now not possible to use types to the right of -> directly for a requirement. Instead a concept, i.e. -> std::same_as<T[1]>, must be used as in your suggested implementation.
well there is difference between (U u), (U& u) and (U&& u) that std::forward is supposed to preserve. in case of (U u) the type has to have defined a copy constructor (since (U u) basically means "pass a copy of")
The 2nd edition of C++ Templates - The Complete Guide features the following footnote at page 436 (my bold):
Except that decltype(call-expression) does not require a nonreference, non-void return type to be complete, unlike call expressions in other contexts. Using decltype(std::declval<T>().begin(), 0) instead does add the requirement that the return type of the call is complete, because the returned value is no longer the result of the decltype operand.
The footnote refers to the fact that decltype(std::declval<T>().begin()) is used (ineffectively, based on the footnote) to test whether it is valid to call .begin() on a T. The code that uses it is the following (with some pieces of text around it for clarity:
the trick is to formulate the expression that checks whether we can call begin() inside a decltype expression for the default value of an additional function template parameter:
#include <utility> // for declval
#include <type_traits> // for true_type, false_type, and void_t
// primary template:
template<typename, typename = std::void_t<>>
struct HasBeginT : std::false_type {};
// partial specialization (may be SFINAE’d away):
template<typename T>
struct HasBeginT<T, std::void_t<decltype(std::declval<T>().begin())>>
: std::true_type {
};
Here, we use decltype(std::declval<T>().begin()) to test whether, given a value/object of type T (using std::declval to avoid any constructor being required), calling a member begin() is valid.
From this previous question of mine, I've understood that since operator, can be overloaded, the role of the , 0 is to trigger the otherwise absent overload resolution, which in turn needs the type of std::declval<T>().begin() to be complete.
However, the text from the book (see the part highlighted in bold above), doesn't mention operator,, nor overload resolution. Is that just bad wording? Or maybe it's just the same matter looked from a different perspective? Or what?
It seems the author forgot or disregarded the possibility of , being overloaded. The whole technique is defective in this regard, not just the wording.
So if begin() is valid and returns a complete type, but , is overloaded and can't be called for some reason, you'll get a false negative.
A more robust solution would be decltype(void(std::declval<T>().begin())).
I wanted to try writing a template wrapper that checks whether a class has a member function.
and for this it was necessary to use std::declval
template<typename T>
struct has_member<T, void_t<decltype(std::declval<T>().push_back())>>:std::true_type{};
As I saw, the implementation of declval should be like this:
template<typename T>
T&& declval() noexcept;
And actually, it may be a odd question ,but why does declval have no return statement ?
If I understand correctly, it should return rvalue to the place of its call:
template<typename T>
struct has_member<T, void_t<decltype(T&&.push_back())>>:std::true_type{};
But we don't use return in the implementation . Maybe it's because we don't have a function body?I would like to understand why this is possible. I would be happy to help
declval has no return statement because the function has no implementation. If you ever tried to call declval, you would get a compile error.
declval exists to be used in what C++ calls an "unevaluated context". This is in a place where an expression will be parsed, the types used worked out, but the expression will never actually be evaluated. The expression given to decltype is an unevaluated context.
Even though declval has no implementation, it is a function with a well-defined return type. So even though you can't actually execute it, the compiler does know what the type of declval<T>() will be. And therefore, the compiler can examine the expression containing it.
See, T&& is a type; you cannot use . on a type. The result of calling a function is an object (or void), which has a type, but isn't itself a type. What you want to say is "assuming I have a value of type T, I want to do X to it". You can't say that with T&& because it's a type, not an object. And you don't want to limit T to things which are default constructible, so you can't just say T{}.
That's where declval comes in.
From cppreference:
Note that declval can only be used in unevaluated contexts and is not required to be defined; it is an error to evaluate an expression that contains this function. Formally, the program is ill-formed if this function is odr-used.
The usage in your code can be simplified to
using type = decltype(std::declval<T>().push_back());
And decltype:
Inspects the declared type of an entity or the type and value category of an expression.
std::declval is never called, so it needs no definition. The declaration is enough for the compiler to deduce its return type.
In other words...
But we don't use return in the implementation
declval has no implementation.
Last week Eric Niebler tweeted a very compact implementation for the std::is_function traits class:
#include <type_traits>
template<int I> struct priority_tag : priority_tag<I - 1> {};
template<> struct priority_tag<0> {};
// Function types here:
template<typename T>
char(&is_function_impl_(priority_tag<0>))[1];
// Array types here:
template<typename T, typename = decltype((*(T*)0)[0])>
char(&is_function_impl_(priority_tag<1>))[2];
// Anything that can be returned from a function here (including
// void and reference types):
template<typename T, typename = T(*)()>
char(&is_function_impl_(priority_tag<2>))[3];
// Classes and unions (including abstract types) here:
template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];
template <typename T>
struct is_function
: std::integral_constant<bool, sizeof(is_function_impl_<T>(priority_tag<3>{})) == 1>
{};
But how does it work?
The general idea
Instead of listing all the valid function types, like the sample implementation over on cpprefereence.com, this implementation lists all of the types that are not functions, and then only resolves to true if none of those is matched.
The list of non-function types consists of (from bottom to top):
Classes and unions (including abstract types)
Anything that can be returned from a function (including void and reference types)
Array types
A type that does not match any of those non-function types is a function type. Note that std::is_function explicitly considers callable types like lambdas or classes with a function call operator as not being functions.
is_function_impl_
We provide one overload of the is_function_impl function for each of the possible non-function types. The function declarations can be a bit hard to parse, so let's break it down for the example of the classes and unions case:
template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];
This line declares a function template is_function_impl_ that takes a single argument of type priority_tag<3> and returns a reference to an array of 4 chars. As is customary since the ancient days of C, the declaration syntax gets horribly convoluted by the presence of array types.
This function template takes two template arguments. The first is just an unconstrained T, but the second is a pointer to a member of T of type int. The int part here does not really matter, ie. this will even work for Ts that do not have any members of type int. What it does though is that it will result in a syntax error for Ts that are not of class or union type. For those other types, attempting to instantiate the function template will result in a substitution failure.
Similar tricks are used for the priority_tag<2> and priority_tag<1> overloads, which use their second template arguments to form expressions that only compile for Ts being valid function return types or array types respectively. Only the priority_tag<0> overload does not have such a constraining second template parameter and thus can be instantiated with any T.
All in all we declare four different overloads for is_function_impl_, which differ by their input argument and return type. Each of them takes a different priority_tag type as argument and returns a reference to a char array of different unique size.
Tag dispatching in is_function
Now, when instantiating is_function, it instantiates is_function_impl with T. Note that since we provided four different overloads for this function, overload resolution has to take place here. And since all of these overloads are function templates, that means SFINAE has a chance to kick in.
So for functions (and only functions) all of the overloads will fail except the most general one with priority_tag<0>. So why doesn't instantiation always resolve to that overload, if it's the most general one? Because of the input arguments of our overloaded functions.
Note that priority_tag is constructed in such a way that priority_tag<N+1> publicly inherits from priority_tag<N>. Now, since is_function_impl is invoked here with priority_tag<3>, that overload is a better match than the others for overload resolution, so it will be tried first. Only if that fails due to a substitution error the next-best match is tried, which is the priority_tag<2> overload. We continue in this way until we either find an overload that can be instantiated or we reach priority_tag<0>, which is not constrained and will always work. Since all of the non-function types are covered by the higher prio overloads, this can only happen for function types.
Evaluating the result
We now inspect the size of the type returned by the call to is_function_impl_ to evaluate the result. Remember that each overload returns a reference to a char array of different size. We can therefore use sizeof to check which overload was selected and only set the result to true if we reached the priority_tag<0> overload.
Known Bugs
Johannes Schaub found a bug in the implementation. An array of incomplete class type will be incorrectly classified as a function. This is because the current detection mechanism for array types does not work with incomplete types.
Edit, in order to avoid confusion: decltype does not accept two arguments. See answers.
The following two structs can be used to check for the existance of a member function on a type T during compile-time:
// Non-templated helper struct:
struct _test_has_foo {
template<class T>
static auto test(T* p) -> decltype(p->foo(), std::true_type());
template<class>
static auto test(...) -> std::false_type;
};
// Templated actual struct:
template<class T>
struct has_foo : decltype(_test_has_foo::test<T>(0))
{};
I think the idea is to use SFINAE when checking for the existance of a member function, so in case p->foo() isn't valid, only the ellipses version of test, which returns the std::false_type is defined. Otherwise the first method is defined for T* and will return std::true_type. The actual "switch" happens in the second class, which inherits from the type returned by test. This seems clever and "lightweight" compared to different approaches with is_same and stuff like that.
The decltype with two arguments first looked surprising to me, as I thought it just gets the type of an expression. When I saw the code above, I thought it's something like "try to compile the expressions and always return the type of the second. Fail if the expressions fail to compile" (so hide this specialization; SFINAE).
But:
Then I thought I could use this method to write any "is valid expression" checker, as long as it depends on some type T. Example:
...
template<class T>
static auto test(T* p) -> decltype(bar(*p), std::true_type());
...
http://ideone.com/dJkLPF
This, so I thought, will return a std::true_type if and only if bar is defined accepting a T as the first parameter (or if T is convertible, etc...), i.e.: if bar(*p) would compile if it was written in some context where p is defined of type T*.
However, the modification above evaluates always to std::false_type. Why is this? I don't want to fix it with some complicated different code. I just want to know why it doesn't work as I expected it to. Clearly, decltype with two arguments works different than I thought. I couldn't find any documentation; it's only explained with one expression everywhere.
It's an comma-separated list of expressions, the type is identical to the type of the last expression in the list. It's usually used to verify that the first expression is valid (compilable, think SFINAE), the second is used to specify that decltype should return in case the first expression is valid.
decltype does not take two arguments. Simply, it can can have an expression as its argument, and the comma operator is one way of creating expressions. Per Paragraph 5.18/1:
[...] A pair of expressions separated by a comma is evaluated left-to-right; the left expression is a discarded-value
expression (Clause 5). Every value computation and side effect associated with the left expression
is sequenced before every value computation and side effect associated with the right expression. The type
and value of the result are the type and value of the right operand; the result is of the same value category
as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field. If the value of the right
operand is a temporary (12.2), the result is that temporary.
Therefore:
static_assert(std::is_same<decltype(42, 3.14), double>::value, "Will not fire");