In regard to C++ Standard:
std::function
of GNU Compiler Collection use union
data type to cast between different function pointer types (e.g. to convert non-static member function pointer to non-member function pointer)? union
data type but no cast is made (type-erasure). undefined behavior
to cast between different function pointer types (in C++ or C++11 Standard)? I think so.std::function
without using any code which has an undefined behavior
? The following is my question:
Do we sometimes have to write code that has undefined behavior
according to the C++ Standard (but they have defined behavior
for particular C++ compilers such as GCC or MSVC)?
Does it mean that we can't/shouldn't prevent undefined behavior
of our C++ codes?
When we run a code, sometimes we see absurd results instead of expected output. So, in C/C++ programming, undefined behavior means when the program fails to compile, or it may execute incorrectly, either crashes or generates incorrect results, or when it may fortuitously do exactly what the programmer intended.
It exists because of the syntax rules of C where a variable can be declared without init value. Some compilers assign 0 to such variables and some just assign a mem pointer to the variable and leave just like that. if program does not initialize these variables it leads to undefined behavior.
According to the C standards, signed integer overflow is undefined behaviour too. A few compilers may trap the overflow condition when compiled with some trap handling options, while a few compilers simply ignore the overflow conditions (assuming that the overflow will never happen) and generate the code accordingly.
In C/C++ bitwise shifting a value by a number of bits which is either a negative number or is greater than or equal to the total number of bits in this value results in undefined behavior.
Nobody forces you to write anything, so nobody forces you to write code that invokes UB.
As for the standard library, its code is free to contain any nonportable behavior it wants - heck, it may even be written in another language with the compiler creating the bindings via magical unicorns; all that matters is that it behaves according to specification.
Come to think of it, it's obvious that at some level the standard library will have to go outside the standard - making syscalls, talking with hardware, ... is not even contemplated by the standard, and is often deeply platform-specific. For example, on 64 bit Linux you can perform syscalls with inline assembly (via the sysenter
instruction) - the standard does not forbid this, it just doesn't mandate that every platform must behave like this.
As for the specific example, I don't see any UB - the union
s there are used as specified by the standard - i.e. reading only from the last member you wrote into (hence the field m_flag
).
__gnu_cplusplus_builtin_std_function__
for all we know.The rest of the question is ill-posed, addressed in a comment.
Here's a rudimentary mock-up of std::function
on the back of an envelope, with no casts or unions or AFAICT anything remotely dangerous. Of course not all features of real std::function
either, but that's just a matter of some technical work.
#include <memory> #include <iostream> #include <type_traits> template <typename R, typename ... Args> struct CallBase { virtual R operator()(Args... args) = 0; virtual ~CallBase() {} }; template <typename R, typename ... Args> struct FunCall : CallBase<R, Args...> { virtual R operator()(Args... args) { return f(args...); } R(*f)(Args...); FunCall(R f(Args...)) : f(f) {} }; template <typename Obj, typename R, typename ... Args> struct ObjCall : CallBase<R, Args...> { virtual R operator()(Args... args) { return o(args...); } Obj o; ObjCall(Obj o) : o(o) {} }; template <typename R, typename ... Args> struct MemFunCall; template <typename R, typename Cl, typename ... Args> struct MemFunCall<R, Cl, Args...> : CallBase<R, Cl, Args...> { typedef typename std::remove_reference<Cl>::type Rcl; virtual R operator()(Cl c, Args... args) { return (c.*f)(args...); } R (Rcl::*f)(Args...); MemFunCall(R (Rcl::*f)(Args...)) : f(f) {} }; template <typename Fn> class Function; template <typename R> struct Function<R()> { std::unique_ptr<CallBase<R>> fn; R operator()() { return (*fn)(); } Function(R (*f)()) : fn(new FunCall<R>(f)) {} template<typename Obj> Function(Obj o) : fn(new ObjCall<Obj, R>(o)) {} }; template <typename R, typename Arg1, typename ... Args> struct Function<R(Arg1, Args...)> { std::unique_ptr<CallBase<R, Arg1, Args...>> fn; R operator()(Arg1 arg1, Args... args) { return (*fn)(arg1, args...); } Function(R (*f)(Arg1 arg1, Args...)) : fn(new FunCall<R, Arg1, Args...>(f)) {} template<typename T> Function(R (T::*f)(Args...)) : fn(new MemFunCall<R, Arg1, Args...>(f)) {} template<typename Obj> Function(Obj o) : fn(new ObjCall<Obj, R, Arg1, Args...>(o)) {} }; struct Foo { static void bar (int a) { std::cout << "bar " << a << std::endl; } int baz (const char* b) { std::cout << "baz " << b << std::endl; return 0; } void operator()(double x) { std::cout << "operator() " << x << std::endl; } }; int main () { Function<void(int)> f1(&Foo::bar); f1(3); Foo foo; Function<int(Foo&, const char*)> f2(&Foo::baz); f2(foo, "whatever"); Function<void(double)> f3(foo); f3(2.75); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With