I have a Derived class that inherits from multiple Base classes. Each of these base classes has its own get function. At runtime I have an index corresponding to the base class whose get function I want to call.
I have hundreds of these Base classes so I was thinking of storing their get functions in a constexpr array of pointer-to-member functions, but I ran into a number of inconsistencies between clang, gcc, and msvc and now I'm not sure what the correct behavior is.
I have a different version of the code that fills this array at runtime and it works perfectly, but it's doing needless work.
I reduced the problem to two base classes instead of hundreds and tried three types of pointer-to-member function calls:
runtime downcasting from base to derived
compile time downcasting from base to derived (outside of class definition)
compile time downcasting from base to derived (inside class definition)
I tried three compilers with C+20 support:
Clang works for all three
GCC fails for #3
MSVC fails for #2 and #3
On GCC and MSVC it doesn't set the this pointer to the correct base class. It always uses the address of the first base class, printing 0 instead of 1.
#include <iostream>
struct Base0
{
int value = 0;
void get() { std::cout << value << std::endl; }
};
struct Base1
{
int value = 1;
void get() { std::cout << value << std::endl; }
};
struct Derived : Base0, Base1
{
#ifdef _MSC_VER // static constexpr downcast pointer-to-member fails in msvc
static const inline auto Get0 = reinterpret_cast<void(Derived::*)()>(&Base0::get);
static const inline auto Get1 = reinterpret_cast<void(Derived::*)()>(&Base1::get);
#else
static constexpr auto Get0 = static_cast<void(Derived::*)()>(&Base0::get);
static constexpr auto Get1 = static_cast<void(Derived::*)()>(&Base1::get);
#endif
};
auto runtimeGet0 = static_cast<void(Derived::*)()>(&Base0::get);
auto runtimeGet1 = static_cast<void(Derived::*)()>(&Base1::get);
constexpr auto constexprGet0 = static_cast<void(Derived::*)()>(&Base0::get);
constexpr auto constexprGet1 = static_cast<void(Derived::*)()>(&Base1::get);
int main()
{
Derived d;
// 1
(d.*runtimeGet0)(); // clang: 0, gcc: 0, msvc: 0
(d.*runtimeGet1)(); // clang: 1, gcc: 1, msvc: 1
// 2
(d.*constexprGet0)(); // clang: 0, gcc: 0, msvc: 0
(d.*constexprGet1)(); // clang: 1, gcc: 1, msvc: 0
// 3
(d.*Derived::Get0)(); // clang: 0, gcc: 0, msvc: 0
(d.*Derived::Get1)(); // clang: 1, gcc: 0, msvc: 0
}
Try it here
Questions:
Which compiler is doing the right thing?
Why would storing the pointer-to-member function inside the class definition vs outside make a difference?
Why does MSVC need reinterpret_cast? Note that I'm using the /vmg and /vmm compiler flags. If I comment out the ifdef I get the peculiar error:
error C2440: 'static_cast': cannot convert from 'void (__cdecl
Outer::Base0::* )(void)' to 'void (__cdecl Outer::Derived::* )(void)'
note: Cast from base to derived requires dynamic_cast or static_cast
The implicit member pointer conversion (static_cast is not required) requires that the derived class is complete, which it isn't in your static member initializers, which are not a complete-class context, see [conv.mem]/2.
The result of using reinterpret_cast is unspecified except for the round-trip conversion back to the original type. Using it directly in a member access causes undefined behavior. See [expr.reinterpret.cast]/10.
So, MSVC is correct to reject the static_cast and Clang and GCC should issue a diagnostic for it as well. Given that this should be ill-formed, there is no sense in asking whether the behavior of the program produced by GCC and Clang for #3 is wrong except if the developers intended this to be allowed as an extension in which case I wouldn't know where the intended behavior is documented. constexpr should not change anything about this.
Because using reinterpret_cast like this causes undefined behavior, MSVC is also not wrong in outputting unexpected results for #3, again except if MSVC is intending to support this as an extension, in which case I don't know the intended behavior.
For #2 (d.*constexprGet1)() MSVC is wrong (according to standard-conformance) to output 0. It should be 1. constexpr does not affect the value of the member pointer.
Related
I was interested in learning more about the behaviour of std::bind() with member functions, and thought it was odd for MSVC to compile the following code when no other compiler I tried would. Is MSVC wrong to compile this?
I tried GCC, CLang, and some other minor compilers in the compiler explorer side-by-side with MSVC: https://godbolt.org/z/DNtP-o
Only MSVC would compile this:
#include <functional>
struct S{
void f(int){}
};
int main(){
S s;
auto binding = std::bind(&S::f,s, 5,3);
return 0;
}
These are the errors I get from compiling with 'x86-64 clang 8.0.0':
error: static_assert failed due to requirement 'integral_constant<bool, false>::value ? sizeof...(_BoundArgs) >= integral_constant<unsigned long, 1>::value + 1 : sizeof...(_BoundArgs) == integral_constant<unsigned long, 1>::value + 1' "Wrong number of arguments for pointer-to-member"
error: no matching function for call to 'bind'
None of the compilers are wrong here, since the program has undefined behavior.
Note that MSVC does of course give errors if you actually try to call the bound functor. So the question is fairly academic, since in a real program using std::bind to create something that can never be called on any code path is not very useful.
C++17 [func.bind.bind]/2 says:
Requires: ... INVOKE(fd, w_1, w_2, ..., w_N) shall be a valid expression for some values w_1, w_2, ... w_N, where N has the value sizeof...(bound_args). ...
But this is a requirement on code using the standard library, not on implementations (compilers and/or libraries), and nothing says implementations must diagnose a violation. Violating a Requires: paragraph is undefined behavior unless otherwise noted.
So the ability of g++ and clang++ (when using libstdc++) to notice the problem at the std::bind call is just a nice quality of implementation bonus feature.
Presumably libstdc++ does this by template matching on a pointer to (member) function. But in the more general case, determining whether there might be any way to call a functor with a given number of arguments of wildcard types is more difficult or impossible. All three compilers accept this code with no errors or warnings:
#include <functional>
struct X {
void operator()() const;
void operator()(int) const;
};
void f() {
auto func = std::bind(X{}, 2, 3, 4);
static_cast<void>(func);
}
While writing a custom reflection library I encountered a strange compiler behavior. However I was able to reproduce the problem with a much simplified code. Here is:
#include <iostream>
class OtherBase{};
class Base{};
/* Used only as a test class to verify if the reflection API works properly*/
class Derived : Base, OtherBase
{
public:
void Printer()
{
std::cout << "Derived::Printer() has been called" << std::endl;
}
};
/*Descriptor class that basically incapsulate the address of Derived::Printer method*/
struct ClassDescriptor
{
using type = Derived;
struct FuncDescriptor
{
static constexpr const auto member_address{ &type::Printer };
};
};
int main()
{
Derived derived;
auto address{ &Derived::Printer };
(derived.*address)(); // -> OK it compiles fine using the local variable address
(derived.*ClassDescriptor::FuncDescriptor::member_address)(); // -> BROKEN using the address from the descriptor class cause fatal error C1001 !
}
While trying debugging this problem I noticed that:
It happen only if Derived has multiple inheritance.
If I swap static constexpr const auto member_address{ &type::Printer } with inline static const auto member_address{ &type::Printer } it works.
Is it just a compiler bug, or I'm doing something wrong ?
Can I solve this problem while keeping the constexpr ?
Please note that I'm using MSVC 2017 with the compiler version 19.16.27024.1
All compiler options are default except for /std:c++17 enabled.
I know that updating (and surely i'll do it) the compiler version to the last one will probably solve the issue, but for now I would like to understand more about this problem.
About C1001, Microsoft Developer Network suggests that you remove some optimizations in your code: Fatal Error C1001. Once you've worked out which optimization is causing the issue, you can use a #pragma to disable that optimization in just that area:
// Disable the optimization
#pragma optimize( "", off )
...
// Re-enable any previous optimization
#pragma optimize( "", on )
Also, a fix for this issue has been released by Microsoft. You could install the most recent release.
const and constexpr:
const declares an object as constant. This implies a guarantee that once initialized, the value of that object won't change, and the compiler can make use of this fact for optimizations. It also helps prevent the programmer from writing code that modifies objects that were not meant to be modified after initialization.
constexpr declares an object as fit for use in what the Standard calls constant expressions. But note that constexpr is not the only way to do this.
When applied to functions the basic difference is this:
const can only be used for non-static member functions, not functions in general. It gives a guarantee that the member function does not modify any of the non-static data members.
constexpr can be used with both member and non-member functions, as well as constructors. It declares the function fit for use in constant expressions. The compiler will only accept it if the function meets certain criteria (7.1.5/3,4), most importantly:
The function body must be non-virtual and extremely simple: Apart from typedefs and static asserts, only a single return statement is allowed. In the case of a constructor, only an initialization list, typedefs, and static assert are allowed. (= default and = delete are allowed, too, though.)
As of C++14, the rules are more relaxed, what is allowed since then inside a constexpr function: asm declaration, a goto statement, a statement with a label other than case and default, try-block, the definition of a variable of non-literal type, definition of a variable of static or thread storage duration, the definition of a variable for which no initialization is performed.
The arguments and the return type must be literal types (i.e., generally speaking, very simple types, typically scalars or aggregates)
When can I / should I use both, const and constexpr together?
A. In object declarations. This is never necessary when both keywords refer to the same object to be declared. constexpr implies const.
constexpr const int N = 5;
is the same as
constexpr int N = 5;
However, note that there may be situations when the keywords each refer to different parts of the declaration:
static constexpr int N = 3;
int main()
{
constexpr const int *NP = &N;
}
Here, NP is declared as an address constant-expression, i.e. a pointer that is itself a constant expression. (This is possible when the address is generated by applying the address operator to a static/global constant expression.) Here, both constexpr and const are required: constexpr always refers to the expression being declared (here NP), while const refers to int (it declares a pointer-to-const). Removing the const would render the expression illegal (because (a) a pointer to a non-const object cannot be a constant expression, and (b) &N is in-fact a pointer-to-constant).
B. In member function declarations. In C++11, constexpr implies const, while in C++14 and C++17 that is not the case. A member function declared under C++11 as
constexpr void f();
needs to be declared as
constexpr void f() const;
under C++14 in order to still be usable as a const function.
You could refer to this link for more details.
I was interested in learning more about the behaviour of std::bind() with member functions, and thought it was odd for MSVC to compile the following code when no other compiler I tried would. Is MSVC wrong to compile this?
I tried GCC, CLang, and some other minor compilers in the compiler explorer side-by-side with MSVC: https://godbolt.org/z/DNtP-o
Only MSVC would compile this:
#include <functional>
struct S{
void f(int){}
};
int main(){
S s;
auto binding = std::bind(&S::f,s, 5,3);
return 0;
}
These are the errors I get from compiling with 'x86-64 clang 8.0.0':
error: static_assert failed due to requirement 'integral_constant<bool, false>::value ? sizeof...(_BoundArgs) >= integral_constant<unsigned long, 1>::value + 1 : sizeof...(_BoundArgs) == integral_constant<unsigned long, 1>::value + 1' "Wrong number of arguments for pointer-to-member"
error: no matching function for call to 'bind'
None of the compilers are wrong here, since the program has undefined behavior.
Note that MSVC does of course give errors if you actually try to call the bound functor. So the question is fairly academic, since in a real program using std::bind to create something that can never be called on any code path is not very useful.
C++17 [func.bind.bind]/2 says:
Requires: ... INVOKE(fd, w_1, w_2, ..., w_N) shall be a valid expression for some values w_1, w_2, ... w_N, where N has the value sizeof...(bound_args). ...
But this is a requirement on code using the standard library, not on implementations (compilers and/or libraries), and nothing says implementations must diagnose a violation. Violating a Requires: paragraph is undefined behavior unless otherwise noted.
So the ability of g++ and clang++ (when using libstdc++) to notice the problem at the std::bind call is just a nice quality of implementation bonus feature.
Presumably libstdc++ does this by template matching on a pointer to (member) function. But in the more general case, determining whether there might be any way to call a functor with a given number of arguments of wildcard types is more difficult or impossible. All three compilers accept this code with no errors or warnings:
#include <functional>
struct X {
void operator()() const;
void operator()(int) const;
};
void f() {
auto func = std::bind(X{}, 2, 3, 4);
static_cast<void>(func);
}
I came across a problem with g++ with upcasting of a member pointer in a constexpr context using static_cast. See code example.
When compiling with g++ version 6.3 and 7.0 they give a compilation error saying reinterpret_cast is not a constant expression.
While clang version 4.0 gives no error, which I think is correct since there is no reinterpret_cast here.
Is this a bug in g++ or clang? What is the correct behavior?
struct Base {};
struct Derived : Base
{
int i;
};
struct Ptr
{
constexpr Ptr(int Derived::* p) : p(static_cast<int Base::*>(p)){}
int Base::* p;
};
constexpr Ptr constexpr_ptr(&Derived::i);
Compiler output
g++ -c -std=c++14 test.cpp
test.cpp:17:40: in constexpr expansion of ‘Ptr(&Derived::i)’
test.cpp:11:41: error: a reinterpret_cast is not a constant expression
constexpr Ptr(int Derived::* p) : p(static_cast<int Base::*>(p)){}
^~~~~~~~~~~~~~~~~~~~~~~~~~~
GCC presumably misapprehends [expr.static.cast]/12, which permits your cast and notes that
If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the behavior is undefined.
Since Base is indeed a base of the class containing the member, the behaviour should be defined, and the constructor call a constant expression.
I have the following program:
constexpr int flag (int);
template<class Tag>
struct writer {
friend constexpr int flag (Tag) {
return 0;
}
};
template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };
template<
bool B = noexcept (flag (0)),
int = sizeof (dependent_writer<B>)
>
constexpr bool f () {
return B;
}
int main () {
constexpr bool a = f();
constexpr bool b = f();
static_assert( !a, "was not instantiated" );
static_assert( !b, "was not instantiated" ); // here's the difference
}
Interestingly, this program compiles with gcc 5.2 and clang 3.6 in C++11, C++14 and C++17 mode. However, with gcc 4.7, 4.8 and 4.9 the following error occurs:
main.cpp: In function 'int main()':
main.cpp:26:7: error: static assertion failed: was not instantiated
static_assert( !b, "was not instantiated" ); // here's the difference
^
It appears that in the old compilers, the default template parameters of the template function f() are decided upon at the function call site. The first time f() is called, the template class writer<int> gets instantiated finally, which defines the function external function flag(). This changes the value of noexcept(flag(0)), because the compiler can now tell by inspection, that this function is noexcept, since it finds a definition, the second time f() is called. Therefore, the found template arguments are different the second time f() is called.
On the other hand, the newer compilers seem to decide on the default template parameters at the point the template function f() is declared and therefore the result is the same both times.
This former behaviour (with gcc 4.7 to 4.9) appears a bit strange to me. I would like to know, if that behaviour was standard conforming anyways and if not, which part of the standard it is contradicting.
NOTE: The question Compile time template instantiation check has an accepted answer that relies on the depicted behaviour of the old compilers. So answering this question will help to find out, whether the accepted solution is correct.