57

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.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
leemes
  • 44,967
  • 21
  • 135
  • 183

2 Answers2

55

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.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • 2
    Ok, but then, why doesn't it "accept" `bar(*p)` if `bar` accepts a `T`? – leemes Apr 16 '13 at 18:38
  • @leemes In the example you linked `bar` doesn't accept all `T`s, it just accepts `std::ostream`. Therefore `bar(*p)` fails if `*p` yields `std::istream` as in the second call. – Daniel Frey Apr 16 '13 at 18:41
  • 1
    @leemes That is because `std::ostream` is not copyable. Maybe the example is supposed to have `int bar(const std::ostream&){return 0;}`? If you change it to this, it works. – Daniel Frey Apr 16 '13 at 18:46
  • Ok, I now have a different example which has problems (my actual problem): I want to test the validity of `QVariant::fromValue(t)`, more precisely if it would compile (more than its signature). Here, it makes a difference, since it's a templated function which uses the type to query more information, which can fail (within the function itself!). decltype seems to have no problems with that. In this case, it always defines the checker as true. It seems to be not possible with this method to write a "expression will compile" check, but rather just "has the correct signature / types to be valid". – leemes Apr 16 '13 at 19:58
  • @leemes An XY problem :) I suggest you ask a new question with an SSCCE of your real problem as I'm not sure the comment is enough to explain it. – Daniel Frey Apr 16 '13 at 20:00
  • Actually, this question arised when I tried to solve [the actual problem](http://stackoverflow.com/questions/16033719/check-if-type-is-declared-as-a-meta-type-system-for-sfinae) in a way I wanted to avoid, as written in the linked question. ;) Nevermind, in the first place I just wanted to know what decltype with two arguments is. Now I know there is no such thing. ;) Thanks! – leemes Apr 16 '13 at 20:03
  • Hah... There is an alternative I just found, which avoids checking the template function against "compilability": http://qt-project.org/doc/qt-4.8/qmetatype.html#qMetaTypeId ... I'll try that now, however, now it's 100% off-topic ;) – leemes Apr 16 '13 at 20:19
  • Is it so that if the first expression is valid, `decltype` will return the type of the first expression already? Why do we need the second expression? – Ziyuan Aug 24 '22 at 09:12
20

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");
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Ok, this explains what decltype does. But can you explain why my second checker always returns `std::false_type`, i.e. the decltype thing fails to compile (at least this is how I interpret the results, as seen in my ideone code)? – leemes Apr 16 '13 at 18:41
  • 1
    @leemes: Sorry, you dropped your comment half a second before the pizza guy arrived :) Basically, Daniel Frey explained it in his last comment to his answer – Andy Prowl Apr 16 '13 at 18:51
  • @AndyProwl Can you help me understand your `static_assert` code? Won't the comma operator cause `"Will not fire"` to always be returned, independent of the result of: `std::is_same::value`? – Jonathan Mee May 18 '15 at 18:01
  • 1
    @JonathanMee No. `static_assert` takes two arguments. – milleniumbug May 18 '15 at 19:06
  • 1
    @JonathanMee: That is not the comma operator. `static_assert` takes two arguments: a boolean expression and a string literal. If the boolean expression evaluates to `false`, the compile-time assertion fires and the content of the string literal is displayed in the error message. Otherwise, no compile-time failure is triggered. – Andy Prowl May 18 '15 at 19:07