2

Why doesn't decltype(*this) compile? It shows an error message:

error: 'value_type' is not a member of 'const Foo<char>&'

So what exactly is the reason that decltype( *this )::value_type does not compile in the below program:

#include <iostream>
#include <vector>
#include <type_traits>


template <typename charT>
struct Foo
{
    using value_type = charT;

    std::vector<value_type> vec;

    void print( ) const;
};

template <typename charT>
void Foo<charT>::print( ) const
{
    using Foo_t = std::remove_reference_t<decltype( *this )>;

                                   // `decltype( *this )::value_type` does not compile
    if constexpr ( std::is_same_v< typename Foo_t::value_type,
                                   decltype( std::cout )::char_type > )
    {
        // logic
    }
    else if constexpr ( std::is_same_v< typename Foo_t::value_type,
                                        decltype( std::wcout )::char_type > )
    {
        // logic
    }
    else
    {
        static_assert( std::is_same_v< typename Foo_t::value_type,
                                       decltype( std::cout )::char_type > ||
                       std::is_same_v< typename Foo_t::value_type,
                                       decltype( std::wcout )::char_type >,
                       "character type not supported" );
    }
}

int main( )
{
#define IS_CHAR 1

#if IS_CHAR == 1
    using FooChar = Foo<char>;
    FooChar foo;
    foo.vec.resize( 10, '$' );
#else
    using FooWideChar = Foo<wchar_t>;
    FooWideChar foo;
    foo.vec.resize( 10, L'#' );
#endif

    foo.print( );
}

What is special about the this pointer? Why does removing the reference with std::remove_reference_t make it compile? Everything works in the above snippet. But if I replace typename Foo_t::value_type with the more readable decltype( *this )::value_type it won't compile. So I tried my luck by using std::remove_reference_t and managed to come up with the above less straightforward (and less intuitive) solution:

using Foo_t = std::remove_reference_t<decltype( *this )>;

// and then inside the std::is_same_v
std::is_same_v< typename Foo_t::value_type, /* etc */ >
// ...

I know that there are more concise alternatives e.g. std::is_same_v< Foo<charT>::value_type, or even std::is_same_v< charT, but I find the decltype approach more self-explanatory. Are there any other solutions?

digito_evo
  • 3,216
  • 2
  • 14
  • 42
  • `T` and `T&` are different types, just like `T` and `T*`. pointers and references don't have the members of the things the point/refer to. – NathanOliver Sep 16 '22 at 19:37
  • @NathanOliver The question is why is `decltype(*this)` a reference type when the result of `*this` is not a reference. I found a duplicate. – François Andrieux Sep 16 '22 at 19:42
  • @FrançoisAndrieux: the result of `*this` *IS* an lvalue (a kind of reference). That reference can be implicitly converted to something else (usually an rvalue) in many contexts, but a `decltype` is not one of them. – Chris Dodd Sep 16 '22 at 19:48
  • @ChrisDodd I don't believe that is accurate : https://timsong-cpp.github.io/cppwp/expr.unary.op#1 lvalue is a value category that is applies a property of an expression. You may be confusing lvalue (the value category) with lvalue references. – François Andrieux Sep 16 '22 at 19:50
  • They say it takes a minimum of ten years to learn 33% of this language. Not surprising at all. – digito_evo Sep 16 '22 at 19:56
  • 2
    @digito_evo There is a lot of truth to that. I've been using C++ since ~2000 and I still learn new things every week it seems. I'm also pretty sure I'll never understand all of the overload resolution rules. That section of the standard makes my brain hurt just thinking about it ;) – NathanOliver Sep 16 '22 at 19:58

1 Answers1

0

The result of applying unary operator* to a pointer is a reference (lvalue ref) to the pointed at value, not a copy of the pointed at value. So decltype(*this) (or decltype(*foo) for any pointer type) will always be a reference type. There's nothing special about this.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • I don't think it's that easy. Indirection of a pointer is "lvalue referring to the object or function to which the expression points" which is not the same as a reference. Edit : See the duplicate. – François Andrieux Sep 16 '22 at 19:40
  • An "lvalue" is (just) a kind of reference in C++ (there are other kinds of references as well). – Chris Dodd Sep 16 '22 at 19:44
  • lvalue references are a kind of reference. An lvalue expression is different. Something can be an lvalue expression without having a reference type. If we have `int i = 0;` the later use of the expression `i;` is an lvalue expression and doesn't use any reference type. – François Andrieux Sep 16 '22 at 19:46
  • an `lvalue` *can* be a reference, it doesn't have to be. in `int foo = 42; foo;`, `foo;` is a lvalue expression. – NathanOliver Sep 16 '22 at 19:46
  • @NathanOliver: No. In that context `foo` is a declarator, not a reference or an expression. We're talking about C++ here, not C (which has a totally different meaning for `lvalue`) – Chris Dodd Sep 16 '22 at 19:52
  • 1
    @ChrisDodd `foo` is a declarator in `int foo = 42;`. It is an lvalue expression in `foo;`. I have 2 "lines" of code in my comment. – NathanOliver Sep 16 '22 at 19:53
  • @NathanOliver: In the expression `foo;` it is an lvalue and has the type `int &` (given the previous earlier declaration). In a statement like `bar = foo;` it is likewise an lvalue with type `int &` that is implicitly converted into an rvalue (of type `int`) and then stored in `bar` (which itself needs to be a (`something &` type). – Chris Dodd Sep 16 '22 at 19:58
  • 1
    No it doesn't. `foo` wasn't declared as a reference and `decltype(foo)` is `int`, not `int&`: http://coliru.stacked-crooked.com/a/330c00696e7cfbaf – NathanOliver Sep 16 '22 at 20:00
  • That's because when `decltype` is applied to a *name* (rather than an *expression*) it gives the type of the declarator of the name, not the type of the expression. – Chris Dodd Sep 16 '22 at 20:01
  • Try changing your example to `test{};` and see what happens -- the extra parens make it an expression rather than a name and you end up with `int &` – Chris Dodd Sep 16 '22 at 20:06
  • @ChrisDodd `decltype` is a double-duty feature. It can be used to determine the type of an object, and it can determine the value category of an expression, depending on what you give it. It communicates the value category of the the expression by either yielding an rvalue reference or an lvalue reference. The value category of an expression is distinct from the type of whatever it results in. I am as certain that `foo`; is not a reference as I am that it is not a `double`. – François Andrieux Sep 16 '22 at 20:20