Consider this C++11 code:
#include <functional>
#include <cstdlib>
template <typename F>
void test(F &&f) {
auto foo = [f]() {
f();
};
foo();
}
int main() {
test(std::bind(std::puts, "hello"));
return 0;
}
GCC and Clang accept this as valid C++11 code, but Visual Studio 2013 requires the lambda to be declared mutable (auto foo = [f]() mutable { ... }). Otherwise I get this error:
error C3848: expression having type 'const std::_Bind<true,int,int (__cdecl *const )(const char *),const char (&)[6]>' would lose some const-volatile qualifiers in order to call 'int std::_Bind<true,int,int (__cdecl *const )(const char *),const char (&)[6]>::operator ()<>(void)'
Is Visual Studio right to reject this code without mutable, or is it valid C++11?
(Curiously Clang rejects the code if you change std::bind(std::puts, "hello") to std::bind(std::exit, 0) apparently because it considers noreturn to make the function type different; I'm quite sure this is a bug.)
This isn't really about lambdas.
#include <functional>
#include <cstdlib>
int main() {
const auto x = std::bind(std::puts, "hello");
x();
}
This is accepted by GCC, but rejected by MSVC.
The standard is unclear on whether this is valid, IMO. The return value g of std::bind has an unspecified return type for which g(...) is valid and defined in terms of the cv-qualifiers of g, but the standard doesn't actually say that any operator() must be callable for const-qualified objects or references. It strongly implies that this is intended to be valid, because otherwise the reference to g's cv-qualifiers seems useless, but it doesn't actually say it is valid.
Because of that, I think MSVC's behaviour is not what the standard's authors intended, but it may nonetheless conform to what the standard requires.
This looks like a bug in the Visual Studio implementation of bind, returning a type with only a non-const function call operator. It should return a type that forwards all function calls to the bound function object, regardless of its own cv-qualifications.
To summarise the rather opaque language of C++11 20.8.9.1.2, function calls on the result of bind should be forwarded to the bound function object, and so should be allowed if calls on that object would be allowed. So it would be an error if the bound function object weren't callable if const; but here, being a function pointer, it is callable regardless of cv-qualifications.
Related
Came across an interesting issue today started by my own typo. I created a lambda that takes in a reference to a struct and incorrectly set it to a std::function that receives it's argument by value.
Here's a more concise version:
#include <functional>
struct InputStruct
{
int i;
InputStruct(): i(1){}
};
void function_rcv(std::function<bool(InputStruct)> & func_ref)
{
InputStruct in;
func_ref(in);
}
int main()
{
std::function<bool(InputStruct)> my_func = [](InputStruct & in)->bool{return in.i==1;};
function_rcv(my_func);
}
Checking with godbolt shows this compiles successfully with MSVC, but fails for both Clang and GCC.
Interestingly enough, using a primitive instead of a struct fails compilation on all three compilers.
Is this a bug in the MSVC compiler?
In summary: it is not a compiler bug. MSVC accepts this code because of its default non-conforming behavior, but it can be made standard-conforming with a switch.
First of all, I need to clarify std::function's one aspect: it accepts a function (in general, Callable) which signature is not a perfect match, but the parameters can be converted. Consider:
using intFn = void (int);
void fn(short);
intFn *a = fn; // doesn't compile
std::function<intFn> b = fn; // compiles!
Here, intFn a function type which has an int parameter, while the function fn has a short parameter. The simple function pointer a, cannot be set to point to fn, as the type of the parameter differ (int vs short). But, std::function allows this, so b can be set to point to fn.
In your example, std::function has an InputStruct parameter by value, while the lambda has a non-const lvalue reference InputStruct &. When std::function std::forwards its parameter, it becomes an xvalue, which cannot be bound to the lambda's lvalue reference parameter. That's why standard conforming compilers don't accept this code.
Why does MSVC accept this code? Because it has non-conforming behavior by default: it allows binding class temporaries (and xvalues) to non-const lvalue references. You can disable this behavior with /Zc:referenceBinding (or the older /Za option). If you use this switch, MSVC rejects your example.
Consider following program:
#include <iostream>
template <typename T>
void foo(const T* x) {
x();
}
void bar() { std::cout<<"bar() is called\n"; }
int main() {
foo(bar);
}
It compiles fine on clang++ & VC++ but g++ gives following compiler error (See live demo here )
main.cpp: In function 'int main()':
main.cpp:10:9: error: no matching function for call to 'foo(void (&)())'
foo(bar);
^
main.cpp:3:6: note: candidate: template<class T> void foo(const T*)
void foo(const T* x) {
^~~
main.cpp:3:6: note: template argument deduction/substitution failed:
main.cpp:10:9: note: types 'const T' and 'void()' have incompatible cv-qualifiers
foo(bar);
^
I've used -pedantic-errors when using g++ & clang++ and I've used /W4 & /Zaoption when using VC++ compiler. See live demo here & here. So, I want to know how template type parameter T will be deduced here ? If I remove const from the program then it compiles fine on g++ also. If I use const T& then it compiles fine on all 3 compilers. So, how exactly type will be deduced here in these cases ?
Update:
This program fails in compilation on Intel C++ compiler also. See live demo here . So, is this bug in g++ & Intel C++ or bug in Clang++ & VC++ ?
This is essentially CWG issue 1584:
It is not clear whether the following is well-formed or not:
void foo(){}
template<class T> void deduce(const T*) { }
int main() {
deduce(foo);
}
Implementations vary in their treatment of this example.
Which is currently still active. It's not really possible to say which compiler is right. Though as the note from 2015 indicates, the consensus in the CWG is currently that this should be rejected.
To give a little more context, we must remember that a function type with a cv-qualifier-seq has special meaning (think member functions), and is not simply a type the designates something which may not be modified. Moreover, you can't even add the cv qualification in some sneaky manner, as [dcl.fct]/7 illustrates:
The effect of a cv-qualifier-seq in a function declarator is not the
same as adding cv-qualification on top of the function type. In the
latter case, the cv-qualifiers are ignored. [ Note: A function type
that has a cv-qualifier-seq is not a cv-qualified type; there are no
cv-qualified function types. — end note ][ Example:
typedef void F();
struct S {
const F f; // OK: equivalent to: void f();
};
— end example ]
There is no way in the language to form a const qualified function type. And yet, the deduction we need is to have const T deduced as void(). The former is a const qualified type, and it must also be a function type. But that's a type that cannot exist! So how can it be deduced?!
On the other hand there is machinery in the standard to deduce it if you were using a reference instead of a pointer.
So it isn't that clear how this should be resolved. On the one hand the wording today doesn't allow it per-se, but on the other hand machinery for it is already in place for references. So some implementations go ahead and do the same for pointers.
I was amazed to find out that GCC allows functions to return arrays when trailing return type is used instead of normal one. As you probably knows arrays can't be copied so this is quite limited but let me show you some cool example.
#include <iostream>
#include <typeinfo>
using namespace std;
auto func() -> int [5]
{
return {4, 56, 78, 4, 0};
}
int main()
{
cout << func()[2] << endl;
cout << typeid(func).name() << endl;
}
Is this a compiler bug or some new feature?
Interestingly 'typeid' returns 'FA5_ivE' which is demangled as 'int (()) [5]' and this means exactly what you think an function returning array of 5 int's.
EDIT: I tried bounding the returned array into rvalue reference but without any success (used most of the possible forms):
auto &&refArrayTemp{ func() };
Seems that this extensions is rather useless.
This was a bug in gcc (fixed as of 2017-07-03), caused by inconsistent treatment of trailing-return-types.
First note the difference in error message between two attempts to declare a function returning a function:
using Fv = void();
Fv f1(); // error: 'f1' declared as function returning a function
auto f2() -> Fv; // error: function return type cannot be function
The first error comes from decl.c, handling declarators, while the second is a lot deeper into the internals, from tree.c, attempting to build the function type preparatory to generating code.
Trailing-return-types are handled in decl.c 30 lines below the above error - too late to catch it with the above error code, and it is not handled separately.
With arrays, similarly using a trailing-return-type allows us to skip the checks in decl.c, the difference being that function-returning-array is actually valid in terms of gcc's internal representation.
Note that you can't do much with it; gcc doesn't allow you to assign, reference-bind, decay or pass the result of func() to another function:
auto a1 = func();
// error: invalid use of non-lvalue array
auto& a2 = func();
// error: invalid initialization of non-const reference of type 'int (&)[5]' from an rvalue of type 'int [5]'
auto&& a3 = func();
// error: lvalue required as unary '&' operand
Indeed, even your code is rejected at -Wpedantic:
warning: ISO C++ forbids subscripting non-lvalue array
Finally, by exploiting a similar bug (qualifiers are stripped from scalars before handling of trailing-return-types) we can create a function with type int const volatile():
int const volatile g1(); // warning: type qualifiers ignored on function return type
auto g2() -> int const volatile; // OK!!
Latest draft, [dcl.array]/p10:
Functions shall not have a return type of type array or function, although they may have a return type of
type pointer or reference to such things. There shall be no arrays of functions, although there can be arrays
of pointers to functions.
This could be a non-standard GCC extension. It doesn't compile in the trunk version of clang. However, this may also be a bug since it has inconsistent behavior with a non-trailing return type.
I get a real kick out of exploring the unusual corners of C++. Having learned about the real types of functions rather than function pointers from this question, I tried messing around with function typing and came up with this bizarre case:
typedef int Func(int);
int Foo(int x) { return 1; }
int main()
{
const Func*& f = &Foo;
return 0;
}
Since &Foo is an rvalue of type Func*, I figured that I should be able to put it in a const reference, but I get this error from g++ 4.6:
funcTypes.cpp: In function ‘int main()’:
funcTypes.cpp:7:23: error: invalid initialization of non-const reference of type ‘int (*&)(int)’ from an rvalue of type ‘int (*)(int)’
But f is const! It has become apparent to me that the application of const to a function (or reference/reference to pointer etc.) is simply ignored; this code compiles just fine:
template <typename A, typename B>
struct SameType;
template <typename A>
struct SameType<A, A> { };
typedef int Func(int);
int main()
{
SameType<const Func, Func>();
return 0;
}
I'm guessing this is how boost pulls off their is_function type trait, but my question is - why does C++ allow this by ignoring it instead of forbidding it?
EDIT: I realise now that in the first example f is non-const and that const FuncPtr& f = &Foo does work. However, that was just background, the real question is the one above.
But f is const!
No, it's not. You're confusing
const Func*& f = &Foo;
with
Func* const& f = &Foo;
The former is a non-const ref to a const pointer. The latter is a const ref to a non-const pointer.
That's why I always write the const-ness before the */& rather than before the type. I would always write the first case as
Func const*& f = &Foo;
and then read right to left: reference to a pointer to a const Func.
In c++03 it was not ignored, but illformed (and was an sfinae case). I guess they changed that in c++11 because then you can simply have function parameters be const F& and can pass to it rvalue function objects aswell as normal functions.
See this DR which made the change http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#295
&Foo is a pointer. In general, I would suggest avoiding references to pointers (const or no). At least, not unless you know what you're doing.
So you should have:
const Func *f = &Foo;
Or really, you can ditch the const entirely:
Func *f = &Foo;
why does C++ allow this by ignoring it instead of forbidding it?
Because you're talking about two different things.
In C++, there is a difference between a function type and a function pointer. Foo is a function type, specifically int(int). &Foo is a function pointer, of type int(*)(int). A function type degrades into a function pointer, where necessary (much like array types degrade into pointers). But they are distinct (just like arrays).
So your two cases are not the same. Your first case is dealing with a function pointer, and your second case is dealing with a function type (which is what the template argument is deduced as).
As for why function types swallow the const, that's because the values of function types are already implicitly constant. You can't change them. The only operation you can perform on a function type is () (or conversion to function pointer). So a const T is equivalent to T if T is a function type. Visual Studio 2010 actually gives a warning about that.
The following compiles fine:
typedef int Func(int);
int Foo(int x) { return 1; }
int main()
{
Func* const& f = &Foo; //ok
return 0;
}
Be aware that const statements are evaluated along with pointers and references from right to left. The last const to the very left you wrote translates to last possible position right of a Name (C++ FAQ on const placement). Hence
const Func*& f
Is translated by the compiler to
Func const*& f
Hence you get a reference to a constant pointer which is not what you want. Besides I would not use references to function pointer if you do not really have to.
No, f is not const. First of all, it is a reference to some mutable type (that mutable type happens to be a const pointer, i.e. a mutable pointer to something that you promise not to change through that particular pointer). However, with &Foo you are creating a temporary (of type Func*) and you cannot assign a temporary to a mutable reference. You can only assign it to a const reference. And that is precisely what the error message is telling you.
Sometimes the error messages can be a bit cryptic.
I put together an example on ideone to illustrate the types printed by gcc for a variety of things:
#include <iostream>
typedef int(Func)(int);
typedef Func* FuncPtr;
typedef FuncPtr& FuncPtrRef;
typedef FuncPtr const& FuncPtrConstRef;
template <typename T> void DisplayType() { T::foo(); }
int main() {
DisplayType<Func>();
DisplayType<FuncPtr>();
DisplayType<FuncPtrRef>();
DisplayType<FuncPtrConstRef>();
}
The compilation errors give:
Func is of type int ()(int) (not a valid type, should now be fixed in gcc)
FuncPtr is of type int (*)(int)
FuncPtrRef is of type int (*&)(int)
FuncPtrConstRef is of type int (* const&)(int)
In your error message you have int (*&)(int), which is a reference to a non-const pointer to a function type.
You are not allowed to bind an rvalue to a non-const reference.
Note: this has thus nothing to do with const being swallowed, as smparkes correctly diagnosed
My previous question concluded that a distasteful "double cast" might be necessary to use the POSIX makecontext with a C++ lambda function (i.e. function object). Moving on, I'm now faced with a compilation error relating to the following minimal code:
#include <iostream>
#include <ucontext.h>
using namespace std;
template <typename T> void foo() {
ucontext_t c;
auto f = [=](int i){ cout << i << endl; };
makecontext(&c, (void (*) (void)) (void (*)(int)) f, 1, 12345);
}
int main(int argc, char *argv[]) {
foo<int>();
return 0;
}
The error is:
error: invalid cast from type ‘foo() [with T = int]::<lambda(int)>’ to type ‘void (*)(int)’
However, if I remove the unused (in this example) template argument from the foo function, so it becomes void foo();, and change the call to foo() the error disappears. Could someone tell me why? I'm using G++ 4.6.
Edit:
From the comments below, it seems the [=] in the code above causes the lambda to be a "capturing" lambda, regardless of the fact that it doesn't actually capture anything. The [=] is not necessary in my code, alas replacing with [] in GCC 4.6 does not remove the error. I am installing GCC 4.6.1 now...
If you use [=] to induce your lambda, you will not get a function pointer (or an object that is convertible to one). You will get a function object. And no amount of casting is going to allow you to pass that to makecontext. Not in any way that actually works.
According to N3291, the most recent working draft of C++0x:
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.
This is the only place where the specification allows conversion to a function pointer. Therefore, if recent versions of GCC do allow conversion to function pointers for [=], that not in accord with the specification.
Only captureless lambdas are convertible to function pointers; while f technically does not capture anything, it does have a default capture mode of capturing by value (for no apparent reason).
Change [=] to [] in the declaration of f and it should work as expected.
EDIT: The fact that this compiles with more recent versions of GCC (as noted by Kerrek) gives a strong indication that this is merely a compiler bug in the version you're using.