C/C++ linkage convention
Asked Answered
E

1

8

When calling C++ algorithms like copy_if, transform etc which take a unary or binary function as the last argument, can I pass a C library function like atoi or tolower.

For e.g. below calls work fine and give the correct output (tried in ideone)

1) transform (foo, foo+5, bar, atoi);
2) transform (foo, foo+5, bar, ptr_fun(atoi));
3) transform(s.begin(),s.end(),s.begin(), static_cast<int (*)(int)>(tolower));

Is this usage guaranteed to work with all C++ compilers ?

The book thinking in C++ mentions "This works with some compilers, but it is not required to." The reason mentioned is (as I understand it) transform is C++ function and expects its last argument to have same calling convention.

The book also suggests a solution for this problem which is to create a wrapper function like this in a separate cpp file and do not include iostreams header file.

// tolower_wrapper.cpp
string strTolower(string s) {
  transform(s.begin(), s.end(), s.begin(), tolower);
  return s;
} 

This works fine, but I did not understand how this resolves the calling convention issue ? transform is still a c++ function and tolower is still a C function in the strTolower, so how this different calling conventions are handled here.

Eventful answered 18/3, 2014 at 0:21 Comment(8)
When compiled by the same compiler with the same settings, those functions are guaranteed to have a compatible calling convention.Fibroin
Calling conventions are not "C or C++", but things like cdecl, stdcall... and i can´t see a reason why it should break this code.Kettering
What does this have to do with linkage?Fomalhaut
This will always work.. Well as far as I can tell.. I tried it in the following on Linux Mint, MacOS Leopard and Windows 8.1: Works in Clang++, G++, MSVC2010, MSVC2012, MSVC2013, XCode(Obj-C++)..Chamberlain
@Fomalhaut that's what I wanted to understand. The exact explanation given in the thinking in cpp book is "The reason, albeit obscure, is that a library implementation is allowed to give “C linkage” (meaning that the function name does not contain all the auxiliary information that normal C++ functions do) to functions inherited from the C language. If this is the case, the cast fails because transform is a C++ function template and expects its fourth argument to have C++ linkage—and a cast is not allowed to change the linkage." This is specifically with e.g. 3 above.Eventful
Yes, I've just read that ;) But linkage is about visibility of a name (for all I know..) so that "casting is not allowed to change linkage" is obscure, since the cast has nothing to do with names. Maybe the author wanted to say something about calling conventions?Fomalhaut
Oh, there's an exception for "language linkage" in the C++ Standard: (I paraphrase) it might be associated with a particular calling convention etc. Linkage as in (internal/external) linkage is just about external visibility of a name (and you might include how it's mangled), that's what I was thinking of.Fomalhaut
The C++11 workaround is to use a lambda: transform(begin(s), end(s), begin(s), [](char c){ return tolower(c); });Fomalhaut
R
2

The first thing to note, which is not actually part of your question but which might help explain for someone reading this, is that the algorithms can take either a function pointer or a function object as an argument.

A function pointer is just that - a pointer to a function which expects to take a specific set of parameters and return a specific type.

A function object is an instance of a class which has overridden operator().

When expanding the algorithm template, the compiler will be able to see which of the two cases applies and will generate appropriate calling code.

In the case of a C function being used as a binary function in an algorithm, it is a function pointer that you are supplying. You can call a C function from C++, as long as it is declared extern C { ... }.

Many compilers come with header files for the C library functions which include something like this:

#ifdef  __cplusplus
extern "C" {
#endif

/* function declarations here */

#ifdef  __cplusplus
}
#endif

so that if you include a C library header from a C++ program, the contained functions will all be magically available to you. That part, however, is not guaranteed by the standard, which is why your book states it may not work with all compilers.

Another wrinkle is that you are not allowed to cast function pointers to a type with a different language linkage, which in at least some of your examples you are doing, although some compilers do seem to allow that anyway - for example see this GCC Bug.

The other catch, which applies specifically to tolower for example, is that some of the names of C library functions are also names of functions or templates in the C++ std library. For example, the name tolower is also defined in <locale>. This specific case is discussed in this GCC bug report. The use of a wrapper, compiled in a separate compilation unit which does not include the conflicting declarations, would resolve this issue.

Risorgimento answered 18/3, 2014 at 1:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.