Вопрос

While trying to formulate a C macro to ease the writing of non-const member functions calling const member functions with exact same logic (see Chapter 1, Item 3, "Avoiding Duplication in const and Non-const Member Functions" in Effective C++), I believe I came across a decltype() bug in VS2013 Update 1.

I wanted to use decltype(*this) to build a static_cast<decltype(*this) const&>(*this) expression in the aforementioned macro to avoid having the macro call site pass any explicit type information. However, that latter expression doesn't appear to properly add const in some cases in VS2013.

Here's a small block of code I was able to make repo the bug:

#include <stdio.h>

template<typename DatumT>
struct DynamicArray
{
    DatumT* elements;
    unsigned element_size;
    int count;

    inline const DatumT* operator [](int index) const
    {
        if (index < 0 || index >= count)
            return nullptr;

        return &elements[index];
    }

    inline DatumT* operator [](int index)
    {
#if defined(MAKE_THIS_CODE_WORK)
        DynamicArray const& _this = static_cast<decltype(*this) const&>(*this);
        return const_cast<DatumT*>(_this[index]);
#else
        // warning C4717: 'DynamicArray<int>::operator[]' : recursive on all control paths, function will cause runtime stack overflow
        return const_cast<DatumT*>(
                static_cast<decltype(*this) const>(*this)
                [index]
            );
#endif
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    DynamicArray<int> array = { new int[5], sizeof(int), 5 };
    printf_s("%d", *array[0]);
    delete array.elements;

    return 0;
}

(may the first one to blab about not using std::vector be smitten)

You can either compile the above code and see the warning yourself, or refer to my lone comment to see what VC++ would spew at you. You can then ! the defined(MAKE_THIS_CODE_WORK) expression to have VC++ compile the code as how I'm excepting the #else code to work.

I don't have my trusty clang setup on this machine, but I was able to use GCC Explorer to see if clang complains (click to see/compile code). Which it doesn't. However, g++ 4.8 will give you an ‘const’ qualifiers cannot be applied to ‘DynamicArray&’ error message using that same code. So perhaps g++ also has a bug?

Referring to the decltype and auto standards paper (albeit, it's almost 11 years old), the very bottom of page 6 says that decltype(*this) in a non-const member function should be T&, so I'm pretty sure this should be legal...

So am I wrong in trying to use decltype() on *this plus adding const to it? Or is this a bug in VS2013? And apparently g++ 4.8, but in a different manner.

edit: Thanks to Ben Voigt's response I was able to figure out how to craft a standalone C macro for what I'm desire to do.

// Cast [this] to a 'const this&' so that a const member function can be invoked
// [ret_type] is the return type of the member function. Usually there's a const return type, so we need to cast it to non-const too.
// [...] the code that represents the member function (or operator) call
#define CAST_THIS_NONCONST_MEMBER_FUNC(ret_type, ...)   \
    const_cast<ret_type>(                               \
        static_cast<                                    \
            std::add_reference<                         \
                std::add_const<                         \
                    std::remove_reference<              \
                        decltype(*this)                 \
                    >::type                             \
                >::type                                 \
            >::type                                     \
        >(*this)                                        \
        __VA_ARGS__                                     \
    )
// We can now implement that operator[] like so:
return CAST_THIS_NONCONST_MEMBER_FUNC(DatumT*, [index]);

The original desire was to hide this all in a macro, which is why I wasn't wanting to worry about creating typedefs or this aliases. It is still curious that clang in GCC Explorer didn't output a warning...though the output assembly does appear fishy.

Это было полезно?

Решение

You said yourself, decltype (*this) is T&. decltype (*this) const & tries to form a reference to a reference (T& const &). decltype triggers the reference collapsing rule 8.3.2p6. But it doesn't collapse the way you'd like.

You could say decltype(this) const&, but that would be T* const& -- a reference to a const pointer, not a pointer to a const object. For the same reason, decltype (*this) const and const decltype (*this) don't form const T&, but (T&) const. And top-level const on a reference is useless, since references already forbid rebinding.

Perhaps you are looking for something more like

const typename remove_reference<decltype(*this)>::type &

But note that you don't need the cast at all when adding const. Instead of

DynamicArray const& _this = static_cast<decltype(*this) const&>(*this);

just say

DynamicArray const& _this = *this;

These combine to

const typename std::remove_reference<decltype(*this)>::type & this_ = *this;

Still, this is a stupid amount of code for a very simple and pervasive problem. Just say:

const auto& this_ = *this;


FYI here's the text of the reference collapsing rule:

If a typedef-name (7.1.3, 14.1) or a decltype-specifier (7.1.6.2) denotes a type TR that is a reference to a type T, an attempt to create the type "lvalue reference to cv TR" creates the type "lvalue reference to T", while an attempt to create the type "rvalue reference to cv TR" creates the type TR.

decltype(*this) is our decltype-specifier which denotes TR, which is DynamicArray<DatumT>&. Here, T is DynamicArray<DatumT>. The attempt TR const& is the first case, attempt to create lvalue reference to (const) TR, and therefore the final result is T&, not const T&. The cv-qualification is outside the innermost reference.

Другие советы

With regard to your macro

// Cast [this] to a 'const this&' so that a const member function can be invoked
// [ret_type] is the return type of the member function. Usually there's a const return type, so we need to cast it to non-const too.
// [...] the code that represents the member function (or operator) call
#define CAST_THIS_NONCONST_MEMBER_FUNC(ret_type, ...)   \
    const_cast<ret_type>(                               \
        static_cast<                                    \
            std::add_reference<                         \
                std::add_const<                         \
                    std::remove_reference<              \
                        decltype(*this)                 \
                    >::type                             \
                >::type                                 \
            >::type                                     \
        >(*this)                                        \
        __VA_ARGS__                                     \
    )

It's much cleaner to do

// Cast [this] to a 'const this&' so that a const member function can be invoked
template<typename T> const T& deref_as_const(T* that) { return *that; }

// [ret_type] is the return type of the member function. Usually there's a const return type, so we need to cast it to non-const too.
// [...] the code that represents the member function (or operator) call
#define CAST_THIS_NONCONST_MEMBER_FUNC(ret_type, ...)   \
    const_cast<ret_type>(deref_as_const(this)__VA_ARGS__)

It's shorter, self-contained, compatible with C++98 except for __VA_ARGS__, and avoids an unnecessary cast

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top