I want to define macro like
#define DECLARE_FUNCTION(funcName, retType, args) retType funcName(args)
And use it like
DECLARE_FUNCTION(intFunc, int, void);
DECLARE_FUNCTION(voidFunc, void, double, double);
DECLARE_FUNCTION(doubleFunc, double, int, double, double);
expecting that those will expand to
int intFunc(void);
void voidFunc(double, double);
double doubleFunc(int, double, double);
This is certainly not working, as macro defined with three arguments eats all the “redundant” arguments and result is
int intFunc(void);
void voidFunc(double);
double doubleFunc(int);
I don’t mind defining macros for different cases, like
DECLARE_FUNCTION_WITH_0_ARGS, DECLARE_FUNCTION_WITH_1_ARG, DECLARE_FUNCTION_WITH_2_ARGS, etc. But the problem is that these macros are not as primitive as I gave in the example, they contain a lot of lines of code, and it would be nice not to rewrite them, but to define only one nontrivial macro, e.q. DECLARE_FUNCTION_WITH_1_ARG, and call it from bodies of all other macros.
You can have a variable number of arguments in your macro. They can be accessed by using special symbols like __VA_ARGS__ inside the macro.
Here's the syntax in standard C.
#define DECLARE_FUNCTION(funcName, retType, ...) retType funcName(__VA_ARGS__)
The ... stands for all the dynamic arguments and is accessed by __VA_ARGS__. Note that you need at least one dynamic argument, otherwise you get a compiler error.
GNU C++ introduces extensions to prevent this from happening. So you can alternatively declare the above as:
#define DECLARE_FUNCTION(funcName, retType, ...) retType funcName(##__VA_ARGS__)
Here are some examples:
DECLARE_FUNCTION(func1, void) becomes void func1() (only with extensions).
DECLARE_FUNCTION(func2, int, int, char) becomes int func2(int, char)
This feature is called "variadic macros". You can read more here.
Related
From § 8.3.5.11 of ISO/IEC 14882:2011(E):
A typedef of function type may be used to declare a function but shall not be used to define a function
The standard goes on to give this example:
typedef void F();
F fv; // OK: equivalent to void fv();
F fv { } // ill-formed
void fv() { } // OK: definition of fv
What motivates this rule? It seems to limit the potential expressive usefulness of function typedefs.
Though this question is about C++, but since C++ inherits typedef and function pointer from C, so an explanation of the same question in C can be used in here. There's a formal explanation for C.
Rationale for International Standard - Programming Languages C §6.9.1 Function definitions
An argument list must be explicitly present in the declarator; it cannot be inherited from a typedef (see §6.7.5.3). That is to say, given the definition:
typedef int p(int q, int r);
the following fragment is invalid:
p funk // weird
{ return q + r ; }
Some current implementations rewrite the type of, for instance, a char parameter as if it were declared int, since the argument is known to be passed as an int in the absence of a prototype. The Standard requires, however, that the received argument be converted as if by assignment upon function entry. Type rewriting is thus no longer permissible.
It's probably mostly historical reasons. typedef was a relatively late addition to C, and was tacked onto the existing language (and caused a few problems for the parsing phase of compilers).
Also, a function definition has to define the names of the parameters, if any. A function type includes the function's return type and parameter types, but not its parameter names. For example, these:
void (int)
void (int x)
void (int y)
are three ways of writing the same function type. If you had:
typedef void func_t(int);
then this hypothetical definition:
func_t some_func { }
wouldn't define a name for its int parameter. I'm not sure how that could have been resolved in a reasonable manner. It would be possible, I suppose, but it was never done.
But the bottom line is probably just that Dennis Ritchie either didn't think it was worth the effort to define how a typedef could be used in a function definition, or he simply didn't think of it.
Let me put a few words. Consider a statement:
typedef void F(int p1, char* p2);
This statement assigns name F to a function signature void (int, char*); This is definition of an alias to the function signature. After that the statement:
F fv;
tells that there is a function fv. It has the signature that was mentioned above and it has its body somewhere. Look at the C/C++ syntax of the function definition:
retType funcName(params) { body }
There are actually 2 names used retType and funcName. None of them are the same to the name F from the initial typedef. The name F has meaning of both names. If language would allow something like:
F { body }
this will associate body with the function type. But this leads a problem:
The meaning of F would be not clear. Is it an "alias to the function signature" or is it a "name of the entry point into a code"?
Plus the syntax of the last example would be weird to millions of C/C++ programmers.
The rule is as you quoted - typedef of function type shall not be used to define a function. In the 3rd line of the example, you are trying to define a function with function type F. This is not allowed by the standard.
EDIT
As you pointed out, I try to explain more on it.
For the 3rd line, if it were legal, then you could replace F with the typedef definition:
void fv { }(). This is not a legal definition or declaration in C++.
I think the key point is that typedef is just creating an alias for simplification, and you can replace your typedef type like replacement of #define during compilation.
As it appears, C++ preprocessor fails if a template instantiation with multiple arguments passed to a macro as an argument.
See an example below.
#include <stdio.h>
#define FOO(v) printf("%d\n",v::val())
template<int N>
struct bar {
static int val() { return N; }
};
template<int N, int M>
struct baz {
static int val() { return N+M; }
};
int main() {
printf("%d\n",bar<1>::val());
printf("%d\n",baz<1,2>::val());
FOO(bar<10>); // OK
FOO(baz<20,30>); // error: too many arguments provided to function-like macro invocation
FOO((baz<20,30>)); // error: '::val' has not been declared
}
Tested with clang++ and g++
Should it be considered as a bug?
No, it's not a bug.
The c preprocessor is a different beast from the rest of the language and it plays by its own rules. Changing this would break compatibility in a massive way, CPP is highly rigorously standardized.
The usual way to work around these comma issues is,
typedef baz<20,30> baz2030_type;
FOO(baz2030_type);
The C/C++ preprocessor recognizes commas as macro argument separators unless they are nested inside parentheses. Just parentheses. Brackets, braces and template markers don't count:
The individual arguments within the list are separated by comma preprocessing tokens, but comma preprocessing tokens between matching inner parentheses do not separate arguments. (C++14 §16.3/11; C11 §6.10.3/11)
(A side effect of the above is that you can use unbalanced braces and brackets as macro arguments. That's usually not a very good idea, but you can do it if you have to.)
Problems occasionally crop up as a result; a common one is unwanted multiple arguments when the argument is supposed to be a block of code:
MY_FANCY_MACRO(1000, { int i=0, j=42; ... })
Here, the macro is called with (at least) 3 arguments, although it was probably written to accept 2.
With modern C++ (and C) compilers, you have a few options. In a fairly subjective order:
Rewrite the macro as an inline function. If the argument is a code block, consider using a templated function which could accept a lambda or other functor. If the argument is a type, make it a template argument instead.
If surrounding the argument with redundant parentheses is syntactically valid, do that. But in such a case it is almost certainly the case that suggestion (1) above would have worked.
Define:
#define COMMA ,
and use it where necessary:
FOO(baz<20 COMMA 30>);
This doesn't require modifying the macro definition in any way, but it will fail if the macro passes the argument to another macro. (The replacement will be done before the inner macro call is parsed, so the multiple argument problem will just be deferred to the inner call.)
If you expect that one macro argument might contain unprotected commas, and it is the last or only argument, and you're in a position to modify the macro, and you're using C++11/C99 or better (or gcc, which has allowed this as an extension for some time), make the macro variadic:
#define FOO(...) printf("%d\n",__VA_ARGS__::val())
The macro's argument is treated as plain text string and the arguments are separated using commas. Hence the comma in the template will be treated as a delimiter. Thus the preprocessor will think that you have passed on two arguments to a single argument macro, hence the error.
BOOST_IDENTITY_TYPE is the solution for that: https://www.boost.org/doc/libs/1_73_0/libs/utility/identity_type/doc/html/index.html
You can also wrap the type into decltype: decltype(std::pair<int, int>()) var; which also adds an extra parentheses, but this unfortunately does an extra ctor call.
It might be not very efficient to talk about the language syntax that has already been set in stone.
I would, however, like to see why the C++11 variadic template's argument expansion couldn't be more explicit to read when it comes to some nested usage.
For example, I think C++ developers could've generally written code more clearly as follows.
(If that's allowed)
template<typename ...> struct Tuple
{
};
template<typename T1, typename T2> struct Pair
{
};
template<class ...Args1> struct zip
{
template<class ...Args2> struct with
{
typedef Tuple<Pair<Args1, Args2>...> type;
// More code but but maybe better readability?
// because Args1, and Args2 are the ones that get
// actually expanded, not Pair<T1, T2>.
// typedef Tuple<Pair<Args1..., Args2...>> type; // ## Invalid! ##
};
};
typedef zip<short, int>::with<unsigned short, unsigned>::type T1;
By doing so we don't really have to get confused which template types are exactly being expanded or should be the ones that will have to be expanded when making multiple-nested variadic template, because then it becomes more important for your code to look clearer than being shortened.
Plus I'd love to see the code in the same manner where I call a function as follows:
Foo(args...); // Okay.
We don't do like this:
Foo(args)...; // Error.
EDIT
I believe this one should've been at least allowed to work nicely.
Tuple<Pair<Args1..., Args2...>> // Invalid code.
But it does not.
Why not?
Is there any potential risk to convince the committee enough to prevent it from compiling?
Thank you so much in advance.
In every one of your cases, your "preferred" notation does a different expansion than the one you want. They aren't invalid.
If Args0 = { a,b,c } and Args1 = { int,double,char } then:
std::tuple< foo< Args0..., Args1... > >
is
std::tuple< foo< a,b,c,int,double,char > >
while
std::tuple< foo< Args0, Args1>... >
is
std::tuple< foo< a, int >, foo< b, double >, foo< c, char > >
The thing is, these are both useful operations.
The key is ... operates on potentially-expanded sub-"expressions", not on packs directly.
Unless your proposed notation can generate both of these results, it is a weaker notation.
The same thing holds true with Foo(Args)... and Foo(Args...). They aren't alternate ways to say something -- they are ways of saying two different things in current C++. Your plan results in in the 2nd meaning the 1st, which means there is no longer a way to express what the 2nd means in your version of C++.
There are two parts to each expansion. The first is what packs are being expanded, the second is what "expression" is being expanded. The packs being expanded, in current C++, are all unexpanded packs within the expression: they are not otherwise marked.
In your version, you mark the packs to expand, and never mark the expression to be expanded. If the language read your mind, it could figure out where that expression was, but programming languages that read your mind are usually a bad idea.
A side effect of "mark the expression, not the pack" is that you can do some seriously powerful stuff, like have entire lambdas that contain unexpanded packs inside expressions inside the body, and then generate a pack of lambdas. Expressing that in your system would be challenging.
I believe I understand this, but I would like some confirmation.
typedef int (NewTypeName) (arg_type* t);
To my undestanding, this type definition allows one to easily declare functions that take 1 argument of type arg_type* and return an int. To declare these functions in a header file, one would write:
NewTypeName func1;
NewTypeName func2;
However, when defining these functions in the .cpp file, I would do it like this:
int func1(arg_type* t) {
//do something
return 1;
}
Am I understanding this correctly?
Yes, it means exactly what you think it means, and yes, that typedef can be used only for a declaration that is not a definition.
Note that the parentheses are unnecessary and unusual here: a more common way to write this is
typedef int NewTypeName (arg_type* t);
The parentheses are only required when you want a pointer-to-function typedef:
typedef int (*NewTypeName) (arg_type* t);
As the comment on your question shows, what you have is very easily misread.
Such typedefs are almost always unnecessary, although they can increase readability when dealing with pointer-to-function parameter or return types: the C function signal can be declared as
typedef void sighandler(int);
sighandler *signal(int signum, sighandler *handler);
or as
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
or as
void (*signal(int sig, void (*func)(int)))(int);
You tell me, which is more readable? I'm guessing you won't choose the third option.
There is one case where the use of a typedef is required: although commonly unimplemented in compilers, C++ compilers are supposed to treat functions with C linkage as different types from functions with C++ linkage. The linkage of names is universally implemented, but the linkage of types is rare. It can be useful to have a template function, which cannot have C linkage, but still give it a type that does have C linkage. This requires a typedef:
extern "C" {
typedef void func_t();
};
template <typename> func_t myfunc;
template <typename T> void myfunc(void) { /* your definition here */ }
That's exactly right, but you should really never declare functions like that. It'll only serve to confuse people. The problem is that there's no way to define a function using the typedef, so the declarations and definitions are inconsistent.
From § 8.3.5.11 of ISO/IEC 14882:2011(E):
A typedef of function type may be used to declare a function but shall not be used to define a function
The standard goes on to give this example:
typedef void F();
F fv; // OK: equivalent to void fv();
F fv { } // ill-formed
void fv() { } // OK: definition of fv
What motivates this rule? It seems to limit the potential expressive usefulness of function typedefs.
Though this question is about C++, but since C++ inherits typedef and function pointer from C, so an explanation of the same question in C can be used in here. There's a formal explanation for C.
Rationale for International Standard - Programming Languages C §6.9.1 Function definitions
An argument list must be explicitly present in the declarator; it cannot be inherited from a typedef (see §6.7.5.3). That is to say, given the definition:
typedef int p(int q, int r);
the following fragment is invalid:
p funk // weird
{ return q + r ; }
Some current implementations rewrite the type of, for instance, a char parameter as if it were declared int, since the argument is known to be passed as an int in the absence of a prototype. The Standard requires, however, that the received argument be converted as if by assignment upon function entry. Type rewriting is thus no longer permissible.
It's probably mostly historical reasons. typedef was a relatively late addition to C, and was tacked onto the existing language (and caused a few problems for the parsing phase of compilers).
Also, a function definition has to define the names of the parameters, if any. A function type includes the function's return type and parameter types, but not its parameter names. For example, these:
void (int)
void (int x)
void (int y)
are three ways of writing the same function type. If you had:
typedef void func_t(int);
then this hypothetical definition:
func_t some_func { }
wouldn't define a name for its int parameter. I'm not sure how that could have been resolved in a reasonable manner. It would be possible, I suppose, but it was never done.
But the bottom line is probably just that Dennis Ritchie either didn't think it was worth the effort to define how a typedef could be used in a function definition, or he simply didn't think of it.
Let me put a few words. Consider a statement:
typedef void F(int p1, char* p2);
This statement assigns name F to a function signature void (int, char*); This is definition of an alias to the function signature. After that the statement:
F fv;
tells that there is a function fv. It has the signature that was mentioned above and it has its body somewhere. Look at the C/C++ syntax of the function definition:
retType funcName(params) { body }
There are actually 2 names used retType and funcName. None of them are the same to the name F from the initial typedef. The name F has meaning of both names. If language would allow something like:
F { body }
this will associate body with the function type. But this leads a problem:
The meaning of F would be not clear. Is it an "alias to the function signature" or is it a "name of the entry point into a code"?
Plus the syntax of the last example would be weird to millions of C/C++ programmers.
The rule is as you quoted - typedef of function type shall not be used to define a function. In the 3rd line of the example, you are trying to define a function with function type F. This is not allowed by the standard.
EDIT
As you pointed out, I try to explain more on it.
For the 3rd line, if it were legal, then you could replace F with the typedef definition:
void fv { }(). This is not a legal definition or declaration in C++.
I think the key point is that typedef is just creating an alias for simplification, and you can replace your typedef type like replacement of #define during compilation.