Say I have implemented a template class like this:
template <size_t N> class C
{
void f()
{
// print out N here?
}
};
I wish that while the compiler compiles a clause like
C<20> c;
it would print out a message
"class C is templated with N = 20"
I've tried with #pragma and static_assert in vain.
The problem is that
with #pragma and static_assert, I could not embed an integral(20 here) into a message;
with preprocessors, it's too early
that N is not substituted with 20
yet.
Is there any way or no way?
Thanks.
You could add a post-build step that finds all instantiations within the output binary after all compilations of the template(s) are complete. For instance, using the GNU toolchain you could do this:
make
nm foo | c++filt | grep 'C<[^>]\+>::f'
Where foo is the name of the output binary.
The regular expression obviously needs to be altered to find the template instantiations you are looking for, but this example works for your example class C.
You could even use a very broad regex to find all template instantiations, of any kind:
grep '<[^>]\+>::'
This is incidentally a great way to illustrate how use of the STL or iostream libraries bloats even seemingly tiny programs. The number of template instantiations can be truly astounding!
In general, generating a warning seems to be a good approach (warning instead of error so you can log multiple things in one compilation run). Here's a function I've been using, which allows passing in a value and get its type printed, or pass a type as a template parameter:
template <typename T>
inline void debug_type(const T&) __attribute__((deprecated));
template <typename T>
inline void debug_type(const T&) { }
template <typename T>
inline void debug_type() __attribute__((deprecated));
template <typename T>
inline void debug_type() { }
You can use it like this:
debug_type(1); // Pass a value, let the compiler deduce its type
debug_type<char>(); // Pass a type explicitly
This results in warnings like this:
foo.cpp:73:17: warning: 'void debug_type(const T&) [with T = int]' is deprecated (declared at /tmp/arduino_build_static/sketch/Num.h:13) [-Wdeprecated-declarations]
debug_type(1);
^
foo.cpp:74:22: warning: 'void debug_type() [with T = char]' is deprecated (declared at /tmp/arduino_build_static/sketch/Num.h:19) [-Wdeprecated-declarations]
debug_type<char>();
The T = int part of the error message shows the type (which can of course be more complicated templated types, this is just an example).
A particular advantage of this warning over the unused variable warning proposed elsewhere is that the error location is the place where debug_type is called, not its implementation, so the code snippet shown in the error shows the expression whose type is being printed (which can be convenient when you want to print a few different ones at once).
Since the pre-processor phase occurs before template instantiation when you compile, you can't have the compiler emit a custom message based on something that a template does using pre-processor directives. Moreover, C++ templates, while extremely powerful, don't have any capacity to emit custom messages at compile time.
I'd go with Dan's approach personally.
If that's not an option, then there's no standard approach, but extending on some of the other options here, it is possible to make the compiler generating warnings for you that will show you the value:
template <int N> class C
{
public:
C ()
{
int d1;
int d1 = d1; // Using uninitialized variable - warning
}
};
C<10> c;
Using g++ with the -Wuninitialized option, the above generates:
t.cc: In constructor 'C<N>::C() [with int N = 10]':
t.cc:7: warning: 'i' is used uninitialized in this function
You could put this into a MACRO enabled for debug builds.
Do you want an error, if N == 20?
If so, how about specialization?
template <> class C <20>
{
int class_C_is_templated_with_20[-1];
}
The following prints out:
Test.cpp: In instantiation of ‘C<20>’:
Test.cpp:14: instantiated from here
Test.cpp:9: error: no matching
function for call to
‘assertion_failed(mpl_::failed************
(C<20>::CLASS_C_TEMPLATED_WITH_I_EQUAL_TO_::************)(mpl_::int_<20>))’
This prints out a semi-legible message, but also halts compilation. I'm not sure if this is what you are looking for.
#include <boost/mpl/assert.hpp>
#include <boost/mpl/int.hpp>
template<int i_>
class C
{
public:
BOOST_MPL_ASSERT_MSG( false, CLASS_C_TEMPLATED_WITH_I_EQUAL_TO_, (boost::mpl::int_<i_>) );
};
int main() {
C<20>();
}
It might be possible to generate a warning (e.g declare an unused instance of a useless empty struct in the f method). However, that warning, which will hopefully also mention the value of N will be triggered (if at all) only when you instantiate the method. On the other hand, it might be conditionally compiled depending on a macro.
Perhaps it will also be possible to put something in the class declaration that invokes the warning when the class itself is instantiated (without, for example, changing the size of the instance). I haven't had any luck triggering a warning while evaluating a static_assert with GCC, though.
Related
I am trying to use SFINAE to disable certain functions of a class based on some non-templated enum arguments.
The following code does NOT compile with gcc, but appears to compile and work as expected when using the msvc compiler.
#include <iostream>
#include <type_traits>
enum class B { VARIANT1, VARIANT2 };
template<B B_VAL>
struct A {
template<class = std::enable_if_t<B_VAL == B::VARIANT1>>
void func1() {
std::cout<<"VARIANT1"<<std::endl;
}
template<class = std::enable_if_t<B_VAL == B::VARIANT2>>
void func2() {
std::cout<<"VARIANT2"<<std::endl;
}
};
int main()
{
A<B::VARIANT1> a;
a.func1();
}
The expected (and msvcs) behavior is that calling a function whose enable_if_t condition equates to false results in a compile time error, or the removal of the function candidate for overload resolution if an overloaded function was present in the example. In all other cases, the code should compile normally.
gcc on the other hand tells me that it can't find a type named "type" in "struct std::enable_if<false, void>" for the enable_if_t in the template of func2, which makes perfect sense as the member named "type" is only present in enable_if if the condition equates to true. But shouldn't this be the desired behavior for the SFINAE functionality and shouldn't the compiler ignore func2, as it is never called?
I now have three questions:
As the two compilers produce different behavior, is it undefined and if yes, which parts/statements?
Is SFINAE suited to achieve my goal, or have I misunderstood its use case?
Would I be better off by using static asserts as alternative?
I am sorry if this question is a duplicate of this one, but I don't think that the answers there provided much help with my problem.
GCC is right. And it's because you aren't using SFINAE for your function. It may appear that you do because you employ utilities for SFINAE from the standard library, but there is a crucial ingredient missing here.
The 'S' in "SFINAE" stands for substitution. The substitution of template arguments into the parameters of a template we are trying to instantiate. Now, the template in question is func2. And for SFINAE to work, it is func2's argument that must fail to be substituted for its parameters. But here
std::enable_if_t<B_VAL == B::VARIANT2>
There is no parameter of func2 in use. It doesn't depend on anything that happens during substitution into func2. It's just an invalid type, completely independent of an attempt to actually instantiate func2.
It's not hard to fix though
template<B B_VAL_ = B_VAL, class = std::enable_if_t<B_VAL_ == B::VARIANT1>>
void func1() {
std::cout<<"VARIANT1"<<std::endl;
}
template<B B_VAL_ = B_VAL, class = std::enable_if_t<B_VAL_ == B::VARIANT2>>
void func2() {
std::cout<<"VARIANT2"<<std::endl;
}
Now, the check is against the substitution into the correct template.
In this question:
Print template typename at compile time
we have a few suggestions regarding how to get typical C++ compilers to print a type's name, at compile time. However, they rely on triggering a compilation error.
My question: Can I get the C++ compiler to print the name of a type without stopping compilation?
In general the answer is "probably not", because a valid program can be compiled into its target object without printing anything anywhere, so I'm asking specifically about GCC and clang, with possible use of preprocessor directives, compiler builtins, or any compiler-specific trick.
Notes:
Obviously, the challenge is printing types behind using/typedef statements, template parameter values, variadic templates etc. If the type is available explicitly you could just use something like #message "my type is unsigned long long" (as #NutCracker suggested). But that's not what the question is about.
Answers relying on C++11 or earlier are preferred to requiring C++14/17/20.
gcc and clang offers some interface for using own plugins which can do nearly everything on different stages from parsing to code generation.
The interfaces are compiler specific and as this a plugin for gcc can not be used for clang or visa versa.
The documentation is havy and there is no chance to go in any detail here, so I only point you to the docs from gcc and clang:
gcc plugin
clang plugin
The following mechanism is due to #JonathanWakely, and is specific to GCC:
int i;
template <typename T>
[[gnu::warning("your type here")]]
bool print_type() { return true; }
bool b = print_type<decltype(i)>();
This gives you:
<source>:In function 'void __static_initialization_and_destruction_0(int, int)':
<source>:7:33: warning: call to 'print_type<int>' declared with attribute warning: your
type here [-Wattribute-warning]
7 | bool b = print_type<decltype(i)>();
| ~~~~~~~~~~~~~~~~~~~~~~~^~
See it working on Godbolt.
In c++17 we can abuse the [[deprecated]] attribute to force the compiler to issue a warning containing the desired template parameter:
template<typename T>
[[deprecated]] inline constexpr void print_type(T&& t, const char* msg=nullptr){}
print_type(999, "I just want to know the type here...");
The snippet above will print the following warning with gcc:
<source>:32:59: warning: 'constexpr void print_type(T&&, const char*) [with T = int]' is deprecated [-Wdeprecated-declarations]
print_type(999, "I just want to know the type here...");
In contrast to the accepted answer this will work with every c++17 compliant compiler. NB that you will have to enable \W3` on MSVC.
We can even go further and define a static assert macro that will print the type if and only if it fails.
template<bool b, typename T>
inline constexpr bool print_type_if_false(T&& t) {
if constexpr (!b)
print_type(std::forward<T>(t));
return b;
}
// Some nice static assert that will print the type if it fails.
#define STATIC_ASSERT(x,condition, msg) static_assert(print_type_if_false<condition>(x), msg);
Here is a live example.
Consider this example, from bug 80985:
template <class Func>
void call(Func f)
{
f();
}
void func() noexcept { }
int main()
{
call(func);
}
Compiling this with all warnings enabled, as you do, yields:
$ g++ -std=c++14 -Wall foo.cxx
foo.cxx:2:6: warning: mangled name for ‘void call(Func) [with Func = void (*)() noexcept]’ will change in C++17 because the exception specification is part of a function type [-Wnoexcept-type]
void call(Func f)
^~~~
What exactly am I supposed to do with this warning? What is the fix?
There's several things you can do about the warning message.
Disable it with -Wno-noexcept-type. In many projects the warning message is unhelpful because there's no chance the resulting object will be linked with an another object that expects it to use GCC's C++17 name mangling. If you're not compiling with different -std= settings and you're not building a static or shared library where the offending function is part of its public interface then the warning message can safely disabled.
Compile all your code with -std=c++17. The warning message will go away as the function will use the new mangled name.
Make the function static. Since the function can no longer be referenced by another object file using a different mangling for the function the warning message will not be displayed. The function definition will have to be included in all compilation units that use it, but for template functions like in your example this is common anyways. Also this won't work for member functions were static means something else.
When calling a function template specify the template parameter explicitly giving a compatible function pointer type that doesn't have the exception specification. For example call<void (*)()>(func). You should also be able to use cast to do this as well, but GCC 7.2.0 still generates a warning even though using -std=c++17 doesn't change the mangling.
When the function isn't a template don't use noexcept with any function pointer types used in the function's type. This and the last point rely on the fact that only non-throwing function pointer types result in naming mangling changes and that non-throwing function pointers can be assigned (C++11) or implicitly converted (C++17) to possibly throwing function pointers.
I'm upvoting Ross's answer for the call<void (*)()>(func) solution. It explicitly tells the compiler that you want the template instantiated for a non-noexcept function type, and guarantees that your code will operate exactly the same in C++17 as it did in C++14.
More alternatives are:
(1) Wrap the noexcept function in a lambda (which isn't noexcept):
template <class Func>
void call(Func f)
{
f();
}
void func() noexcept { }
int main()
{
call([]() { func(); });
}
(2) Create a separate wrapper function without noexcept. This is more typing initially, but if you have multiple call sites it could save typing overall. This is what I ended up doing in the code that originally prompted me to file the GCC bug.
In addition to what is already said, I have found another way to get rid of this warning in GCC 7. Apparently, GCC generates this warning if and only if the first instantiation of call() involves noexcept. So a solution would be first to instantiate call() with a not noexcept function.
This trick would also do:
using dummy = decltype(call(std::declval<void(*)()>()));
P.S. GCC 8.2.1 does not report a warning in this case.
The issue they are warning you about is that in C++14, this will work:
void call(void (*f)())
{
f();
}
void func() noexcept {}
int main(int argc, char* argv[])
{
call(&func);
return 0;
}
but in C++17, you would need to change the declaration of call to be:
void call(void (*f)() noexcept)
{
f();
}
Since you have defined call to be a template, you don't need to worry about this. Still, it could cause you problems because the inferred type is changing, which doesn't usually happen.
For example, this code will compile in C++14 but not C++17:
void foo() noexcept {}
void bar() {}
template <typename F>
void call(bool b, F f1, F f2)
{
if (b)
f1();
else
f2();
}
void foobar(bool b)
{
call(b, &foo, &bar);
}
In C++14, the types of foo and bar are the same, but they are different in C++17, meaning the template resolution will fail. The error message in gcc 7.2 with the flag -std=c++1z is:
note: template argument deduction/substitution failed:
note: deduced conflicting types for parameter 'F' ('void (*)() noexcept' and 'void (*)()')
In the example you've given, there is no issue and you won't have a problem compiling in C++14 or C++17 mode. If the code is more complex than the example here (e.g. similar to the examples I gave above), you could encounter some compiler problems. It seems you have a recent compiler; try compiling with -std=c++1z and see if there are warnings or errors.
I was using old version of gcc so I tried to implement several useful type_traits elements, like is_base_of and static_assert, like this:
template <typename Base, typename Derived>
struct my_is_base_of{
struct Yes{char _;};
struct No{char _[2];};
static Yes _test(const Base*);
static No _test(void*);
static const bool value=sizeof(_test((Derived*)0))==sizeof(Yes);
};
template<bool b>struct _static_assert_test{static char _;};
template<>struct _static_assert_test<false>{};
#define _static_assert(x) _static_assert_test<x>::_
struct Base{};
struct Derived : Base {};
struct C {};
#include<iostream>
int main()
{
std::cout<<std::boolalpha<<my_is_base_of<Base,Derived>::value<<std::endl;
_static_assert(sizeof(int)==4);
_static_assert(my_is_base_of<Base,Derived>::value);//fails to compile
return 0;
}
Well, the 1st line in main functions compiles and prints "true". So does the 2nd line. But the 3rd line fails to compile. My gcc 4.1.2 says:
derive.cpp:22:54: error: macro "_static_assert" passed 2 arguments, but takes just 1
derive.cpp: In function ‘int main()’:
derive.cpp:22: error: ‘_static_assert’ was not declared in this scope
How to fix my case? Thanks a lot.
It is worth noting that C++ macros are expanded before the parsing phase of compilation and this is done by a textual replacement of each parameter of the macro to matched places. my_is_base_of<Base,Derived>::value is here interpreted by the macro as two parameters as it uses comma operator: my_is_base_of<Base becomes first parameter and Derived>::value becomes second. This behavior is precisely due to the fact that macro does not (cannot) perform parsing and as such it is unable to know that comma is used in the context of template parameters. To workaround the problem you need to put the statement in parenthesis:
_static_assert((my_is_base_of<Base,Derived>::value));
compiles without problems.
Consider the following example:
template <class T>
class C
{
public:
C();
C(C&& rhs);
private:
T m_data;
};
template <class T>
C<T>::C()
: m_data(T())
{
}
template <class T>
C<T>::C(C&& rhs)
: m_data(rhs.data)
{
}
int main()
{
C<int> i;
}
Line : m_data(rhs.data) contains an error as C does not have a member named data. But none of the compilers that I tried (gcc 5.2, clang 3.5.1) detected that error.
But when I add the following line to main function the compiler detects the error:
C<int> j = std::move(i);
Why the compiler does not give an error in the first case?
Even if that particular function is not called it can figure out that C has no member named data.
In addition when I change the definition of move constructor to the following:
template <class T>
C<T>::C(C&& rhs)
: m_data(rhs.data)
{
data = 0;
}
the compiler gives error on line data = 0; but not on : m_data(rhs.data).
So the function gets parsed.
There is 2 passes to check error in template.
One for non dependent code
One for dependent code (done at instantiation)
Your code is template dependent, so it is checked only when the method is instantiated.
Your template is ill-formed but the error does not require a diagnostic (in other words, the compiler is allowed to not give an error message and can do anything it wants). More precisely, the Standard says
Similarly, if the id-expression in a class member access expression for which the type of the object expression is the current instantiation does not refer to a member of the current instantiation or a member of an unknown specialization, the program is ill-formed even if the template containing the member access expression is not instantiated; no diagnostic required.
In your code C is the current instantiation and rhs.data is a class member access expression but does not refer to a member of the current instantiation and not to a member of an unknown specialization (which would be the case if C had dependent base classes, i.e if you would have written class C : T or something similar).
To read up on these rules, see this answer . It is also worth nothing that this kind of code has always been ill-formed (no diagnostic required), even in C++03 that didn't have this addition rule that I quoted. Because this code makes the template have no valid instantiation, for all possible types of T. But the existing rule of C++03 is rather broad and this addition of C++11 is a succinct test that allows this kind of code to be safely rejected.
the compiler does not try to compile C(C&& rhs); since you don't call it (in the first try).
this is called the zero overhead rule in C++ - you don't pay on what you don't use.
when you try to call it the compiler than tries to compile the function and fails.
In this case, the compiler obiously couldn't figure out that the move constructor will never work. It is allowed to do so, but not required.
In the general case, it is hard to detect that there is no T whatsoever for which the code could be compiled. If it just fails for C<int> and C<float>, but works for C<my_class>, the compiler must not complain (as long as the function isn't used for int or float).