decltype(*this) bug in VS2013? - gcc

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

Related

Recursively unpacking a template pack for a parameter-less function

I'm trying to create a struct template with a variadic template type pack, that can deduct the sum of the size of all types passed in.
Below you find a simplified example, in the real-world context, the size computed is used to create further member objects.
template <typename... Types>
struct OverallSize
{
template <typename FirstType, typename... NextTypes>
static constexpr size_t sizesum() { return sizeof (FirstType) + sizesum<NextTypes...>(); }
template <typename LastType>
static constexpr size_t sizesum() { return sizeof (LastType); }
static constexpr size_t size = sizesum<Types...>();
};
// Should work e.g. like this
auto s = OverallSize<int, float, char>::size; // s will be 9 on x86-64
I'm used to this recursive parameter unpacking approach when it comes to argument lists and assumed this works as well with argument-less functions and explicit template specification. However I get the following error when compiling with clang
Call to 'sizesum' is ambiguous
...
Candidate function [with FirstType = unsigned long, NextTypes = <>]
Candidate function [with LastType = unsigned long]
So it seems as if the last recursion iteration doesn't work here – not sure why the compiler doesn't simply chose the most obvious choice: The one with only one template type – just as it would happen if there was an actual template argument passed to the function.
So, what do I have to do to make this compile and work as desired?
For C++14 you can use SFINAE:
template <
typename FirstType,
typename... NextTypes,
std::enable_if_t<sizeof...(NextTypes) >= 1>* = nullptr >
static constexpr size_t sizesum() {
return sizeof (FirstType) + sizesum<NextTypes...>();
}
this template will be considered only if parameters pack has size >= 1.
Demo

Remove class member type part from decltype

I ran into I case I had not seen before, while using decltype on a member of a templated class. I wanted to make a nicer make_unique so that changing type on the member does not cause fixing the make_unique calls. I wanted to avoid this using decltype(member)::element_type as the type for make_unique but got an error. Here is a simple snippet that shows the error (and I understand why it is shown):
#include <memory>
template<typename T>
struct foo
{
foo()
{
// g++ gives:
// dependent-name 'decltype (((foo<T>*)this)->foo<T>::p_)::element_type' is parsed as a non-type, but instantiation yields a type
// say 'typename decltype (((foo<T>*)this)->foo<T>::p_)::element_type' if a type is meant
//
// How can I atleast remove the class name from the type?
p_ = std::make_unique<decltype(p_)::element_type>();
// g++ gives:
// dependent-name 'decltype (p)::element_type' is parsed as a non-type, but instantiation yields a type
// say 'typename decltype (p)::element_type' if a type is meant
//
// makes sense since p here is dependent on T
std::unique_ptr<T> p = std::make_unique<decltype(p)::element_type>();
// This one is fine, makes sense, since the type is known
std::unique_ptr<int> p2 = std::make_unique<decltype(p2)::element_type>();
}
std::unique_ptr<T> p_;
};
int main()
{
foo<int> f;
return 0;
}
My question is, is there a nice/pretty way to remove the 'is a member of' ((foo<T>*)this)->foo<T>::p_))part from the decltype value, so that at least I could use the same fix and simply provide typename on the member variable p_ ? The long fix suggested by g++ seems kind of ugly.
5 minutes after posting I had an idea that I could do
p_ = std::make_unique<decltype(std::remove_reference(*p_)::type)>();
but that seems to give a parse error.
You can simply place a typename before decltype().
I mean
p_ = std::make_unique<typename decltype(p_)::element_type>();

How to constraint the parameter package type in c++11? And How to implement the template in cpp?

For the first quesion:
I want to write a function to concatenation the strings, and it can receive multiple strings;
#include <string>
#include <vector>
#include <type_traits>
template <class... Args, typename std::enable_if<std::is_same<typename std::decay<Args...>::type, std::string>::type>::type>
std::string foo(const std::string &first, const Args &... senconds) {
std::string delimiter = "$$";
std::string ret = first;
std::vector<std::string> vec{senconds...};
for (auto second = vec.rbegin(); second != vec.rend(); second++) {
ret = delimiter + *second + delimiter + ret;
}
return ret;
}
but when I invoke it like:
std::string name = "x";
name = foo(name, "xxx");
the compiler will throw an error:
error: no matching function for call to ‘foo(std::__cxx11::string&, const char [4])’
and there will be some note:
note: couldn't deduce template parameter ‘<anonymous>’
I think I should modify the constraint in the template, and I've tried all the related methods in the type_traits, but none of them works.
For the second question:
I want to hide the implementation of some function, but for the template function, it's unable to put the definition in the .hpp, and put the implementation in the .cpp, the compiler will throw a undefined reference error. Is there any elegant way to solve this?
Thanks.
There's a bit to unwrap here.
std::decay<Args...>::type can't work. std::decay takes only a single template argument, but you attempt to expand the pack here. The expansion needs to happen on the is_same.
You are also missing a way to aggregate all the is_same predicates. Do you want to and them all or or them all? Presumably and. In C++17 that's easily done with a fold expression, but for C++11 we have to work a bit.
Finally the thing the compiler complains about: std::enable_if<bla>::type evaluates to void if bla is true. That means you're formally expecting a non-type template argument, and the compiler complains because it can't deduce which value of type void it should deduce. This is normally alleviated by forming a pointer to it instead and defaulting it to nullptr: std::enable_if<bla>::type* = nullptr.
It appears (?) that you expect foo(someString, "stringLiteral"); to work. It won't, because a string literal is not a std::string. Maybe you wanted a different predicate, but for this answer I'll stick with the original condition.
Putting all that together:
In C++17, you would write
template <class... Args,
std::enable_if_t<
(std::is_same_v<std::decay_t<Args>, std::string> && ...)
>* = nullptr
>
https://godbolt.org/z/84Dcmt
In C++11, we use this helper and add back the typename and ::type verbosity:
template <class... Args,
typename std::enable_if<
var_and<
std::is_same<typename std::decay<Args>::type, std::string>::value...
>::value
>::type* = nullptr
>
https://godbolt.org/z/2eFyX7
Base on MaxLanghof's answer, I changed the template to:
template <class... Args,
typename std::enable_if<var_and<std::is_constructible<
std::string, Args>::value...>::value>::type * = nullptr>
In this form, the function foo can be invoked like the name = foo(name, stringRed, "xxx").
Thanks #MaxLanghof again.

Using coeffRef with const CwiseUnaryView - Failing when multiplying two CWiseUnaryViews

I'm having some trouble when using coeffRef() with a CWiseUnaryView function, but only when the function is declared as const
Reproducible example:
#include <Eigen/Core>
struct dummy_Op {
EIGEN_EMPTY_STRUCT_CTOR(dummy_Op)
EIGEN_DEVICE_FUNC
EIGEN_STRONG_INLINE const double&
operator()(const double &v) const { return v; }
EIGEN_DEVICE_FUNC
EIGEN_STRONG_INLINE double&
operator()(double &v) const { return v; }
};
void foo(Eigen::MatrixXd &out)
{
//Compiles
Eigen::CwiseUnaryView<dummy_Op, Eigen::MatrixXd> view(out);
view.coeffRef(0,0);
//Doesn't Compile
const Eigen::CwiseUnaryView<dummy_Op, Eigen::MatrixXd> const_view(out);
const_view.coeffRef(0,0);
}
Returns:
<source>: In function 'void foo(Eigen::MatrixXd&)':
<source>:21:28: error: passing 'const Eigen::CwiseUnaryView<dummy_Op,
Eigen::Matrix<double, -1, -1> >' as 'this' argument discards qualifiers
[-fpermissive]
const_view.coeffRef(0,0);
^
In file included from /opt/compiler-explorer/libs/eigen/v3.3.4/Eigen/Core:413,
from <source>:1:
/opt/compiler-explorer/libs/eigen/v3.3.4/Eigen/src/Core/DenseCoeffsBase.h:340:33: note:
in call to 'Eigen::DenseCoeffsBase<Derived, 1>::Scalar&
Eigen::DenseCoeffsBase<Derived, 1>::coeffRef(Eigen::Index, Eigen::Index)
[with Derived = Eigen::CwiseUnaryView<dummy_Op, Eigen::Matrix<double,
-1, -1> >; Eigen::DenseCoeffsBase<Derived, 1>::Scalar = double; Eigen::Index = long int]'
EIGEN_STRONG_INLINE Scalar& coeffRef(Index row, Index col)
^~~~~~~~
Compiler returned: 1
Compiler explorer: https://godbolt.org/z/kPHPuC
The side-effect of this, is that the multiplication of two (non-const) CWiseUnaryViews also fails, see example here: https://godbolt.org/z/JYQb3d
The bottom line is that you're calling a non-const method of a constant instance. The (first) coeffRef that is being called is the one (and only) in DenseCoeffsBase.h (DenseCoeffsBase<Derived, WriteAccessors>), which is not const qualified. The DenseCoeffsBase<Derived, ReadOnlyAccessors> class does not have a coeffRef method. You can get around this error (and get a warning) if you enable the -fpermissive compiler flag.
In the dense case, you probably want to use the operator()(Index, Index) method anyway, which does have a const qualified version. I just noticed the documentation explicitly says to use that method anyway, even for the non-const version. This is obviously not going to return a const reference, but at least in your example as a double, it shouldn't matter too much.
CwiseUnaryView is intended to be used for L-value like expression, e.g.,
MatrixXcd A;
A.real() = something; // `A.real()` is writable
If you want to apply an element-wise functor and use it as an R-value, you should use CwiseUnaryOp instead:
void foo(Eigen::MatrixXd &out)
{
Eigen::CwiseUnaryOp<dummy_Op, Eigen::MatrixXd> view1(out);
// shorter:
auto view2 = out.unaryExpr(dummy_Op());
Eigen::MatrixXd result = view1 * view2;
// or directly write: out.unaryExpr(dummy_Op()) * out.unaryExpr(dummy_Op());
}

Is this change in overload resolution between Clang 3.5 and 3.6 correct or a bug?

The code below compiles in Visual Studio 2013, gcc 4.8, clang 3.4 and clang 3.5 (Apple LLVM 6.0) but does not compile in clang 3.6 (via Apple LLVM 6.1)
The code is a simplified version of a complicated class in our codebase which is the minimum required to exhibit the issue.
The crux of the problem is that the copy construction of TYPED_VALUE is, in 3.6, evaluating the templated conversion operator for type STRING because of the presence of a constructor that accepts a STRING; this causes std::is_constructible to be evaluated which leads to it needing the definition of STRING (which we cannot provide here - would lead to a circular dependency in the full code).
class STRING;
class TYPED_VALUE
{
public:
TYPED_VALUE( const TYPED_VALUE& ) = default; // explicit or implicit doesn't make a difference
TYPED_VALUE( const STRING & ) {}
template< typename TYPE, typename std::enable_if<!std::is_pointer< TYPE >::value && !std::is_constructible< TYPE, const STRING& >::value && !std::is_constructible< TYPE, bool >::value, int >::type = 0 >
operator TYPE( void ) const = delete;
};
class TYPED_STORAGE
{
public:
TYPED_STORAGE( const TYPED_VALUE &v ) : value( v ) {}
TYPED_VALUE value;
};
The error message is
/type_traits:2329:38: error: incomplete type 'SICORE::STRING' used in type trait expression
: public integral_constant<bool, __is_constructible(_Tp, _Args...)>
^
/main.cpp:348:99: note: in instantiation of template class 'std::__1::is_constructible<SICORE::STRING, const SICORE::STRING &>' requested here
template< typename TYPE, typename std::enable_if<!std::is_pointer< TYPE >::value && !std::is_constructible< TYPE, const STRING& >::value && !std::is_constructible< TYPE, bool >::value, int >::type = 0 >
^
/main.cpp:349:9: note: while substituting prior template arguments into non-type template parameter [with TYPE = SICORE::STRING]
operator TYPE( void ) const = delete;
^~~~~~~~~~~~~~~~~~~~~~~~~~~
/main.cpp:355:56: note: while substituting deduced template arguments into function template 'operator type-parameter-0-0' [with TYPE = SICORE::STRING, $1 = (no value)]
TYPED_STORAGE( const TYPED_VALUE &v ) : value( v ) {}
^
/main.cpp:340:11: note: forward declaration of 'SICORE::STRING'
class STRING;
^
To me this seems like a bug in 3.6, in previous versions the overload resolution determines that the copy constructor is the best fit without having to evaluate the template arguments - I tried to understand the overload resolution notes in the standard but I think that just confused me more ;)
(This can be fixed by making either the constructor or the conversion operator explicit I realise, but that is not the behaviour we want)
Any standard experts out there know the answer?
I believe Clang is correct to produce this error:
The [temp.inst] section of the C++ standard in paragraph 10 says:
If a function template or a member function template specialization is
used in a way that involves overload resolution, a declaration of the
specialization is implicitly instantiated (14.8.3).
Forming the implicit conversion sequence necessary to rank the overload candidates for the call to TYPE_VALUE's constructor requires the instantiation of the conversion operator. And the use of an incomplete type parameter to the trait doesn't form an invalid type, so this isn't a substitution failure, it is a hard error.
The copy constructor of TYPED_VALUE uses a reference to STRING, it should not be evaluated.
I think this is a clang error.
I haven't read the new c++ standard for a long time, however, I couldn't make sure it hadn't changed.
templates are instantiated as-needed, and I think Clang 3.6 implemented a DR where it needed to instantiate a template earlier than 3.5 did.

Resources