if constexpr and C4702 (and C4100, and C4715) - c++

Is there a way to fix the following problem:
This code produces a C4702 warning 'unreachable code' (on VC++ 15.8 with /std:c++17)
template <typename T, typename VariantType>
inline bool MatchMonostate( VariantType& variant )
{
SUPPRESS_C4100( variant );
if constexpr ( std::is_same_v<T, std::monostate> )
{
variant = std::monostate();
return true;
}
return false; // !!! unreachable if the above is true !!! => C4702
}
to suppress the C4100 'unreferenced formal parameter' warning, I'm already using the trick
#define SUPPRESS_C4100(x) ((void)x)
The simple idea of adding
else
{
return false;
}
results in warning C4715 'not all control paths return a value' instead.

It's unreachable because for a given expansion of the template based on the template arguments the function will only ever pass the condition and return true or fail and return false. There is no case where it could go either way for the same type. It's essentially expanding to
if (true) {
return true;
}
return false; // Obviously will never happen
I'd rewrite it to only have a single return statement.
template <typename T, typename VariantType>
inline bool MatchMonostate( VariantType& variant )
{
SUPPRESS_C4100( variant );
bool retval = false;
if constexpr ( std::is_same_v<T, std::monostate> )
{
variant = std::monostate();
retval = true;
}
return retval;
}
Also, in the case where the condition is true variant is not unused. You may want to move that line that suppresses the warning (which basically turns into (void)variant) to an else statement.

As the direct answer to the direct question. On the subject of if constexpr. Consider this:
template <typename T, typename ... params >
inline bool match_monostate
(std::variant<params ...> & variant) noexcept
{
if constexpr (std::is_same_v<T, std::monostate>)
{
variant = std::monostate{} ;
// compiles only if called with variant
// whose one alternative is std::monostate
return true;
}
else {
return false;
}
}
Depending on the bool result of the if constexpr expression, compiler actually produces two functions. This version is produced when if constexpr() yields true:
template <typename T, typename ... params >
inline bool
match_monostate (std::variant<params ...> & variant) noexcept
{
variant = std::monostate{} ;
// compiles only if called with variant
// whose one alternative is std::monostate
return true;
}
This version is produced when if constexpr() yields false:
template <typename T, typename ... params >
inline bool
match_monostate (std::variant<params ...> & variant) noexcept
{
return false;
}
The second version might emit warnings about unused argument. But (it seems) not if using the latest versions of clang/gcc/msvc. For older compilers as "old123987" also pointed out one can add the standard attribute to the signature. Like so:
template <typename T, typename ... params >
inline bool
match_monostate ([[maybe_unused]] std::variant<params ...> & variant) ;
That will stop the warning emitting.

Related

Usage of if constexpr in template

I am trying to understand the utility of if constexpr and want to know if there is any utility in using it in this way.
template<bool B>
int fun()
{
if constexpr (B)
return 1;
return 0;
}
Is this function changed at all by using if constexpr instead of a regular if? I assume the performance would be the same. My understanding of templates is that the outcome of the if statement is already known at compile time so there is no difference.
Utility of constexpr?
A trivial example... if you write the following function
template <typename T>
auto foo (T const & val)
{
if ( true == std::is_same_v<T, std::string>> )
return val.size()
else
return val;
}
and call it with an integer
foo(42);
you get a compilation error, because the instruction
val.size();
has to be instantiated also when val is an int but, unfortunately, int isn't a class with a size() method
But if you add constexpr after the if
// VVVVVVVVV
if constexpr ( true == std::is_same_v<T, std::string>> )
return val.size()
now the return val.size(); instruction is instantiated only when T is std::string, so you can call foo() also with arguments without a size() method.
---- EDIT ----
As #prapin observed in a comment (thanks!), if constexpr can be necessary for an auto function.
I propose another trivial (and silly) example
Without if constexpr, the following bar() function
template <typename T>
auto bar (T const &)
{
if ( true == std::is_same_v<T, std::string>> )
return 42;
else
return 42L;
}
doesn't compile, because the first return return a int value, the second return a long; so, given that without if constexpr the compiler must instantiate both return's, so the compiler can't conciliate the returns types and can't determine the return type of the function.
With if constexpr,
if constexpr ( true == std::is_same_v<T, std::string>> )
return 42;
else
return 42L;
the compiler instantiate the first return or the second one; never both. So the compiler ever determine the type returned from the function (int when called with a std::string, long otherwise).

Variadic template returning a N-tuple based on an unknown number of arguments

I would like to have a variadic function template that takes pointers of a certain type T, fills those pointers, and for each of them generate an object as a result, the final result being a tuple of all these generated objects.
Given a function from a library (that uses a C api):
struct opaque {};
bool fill_pointer(opaque **);
And:
struct MyType {};
MyType gen_object(opaque *);
I would like to have a variadic template function that would look like this (sort of):
std::tuple<bool, MyType...> fill_and_gen_objects(opaque **...);
(where the bool result is false if and only one of fill_pointer return value is false).
This is what I would like to achieve:
opaque *oa, *ob, *oc;
auto [failed, ta, tb, tc] = fill_and_gen_objects(oa, ob, oc);
Thanks
That's heavy pseudocode, I'll answer with heavy pseudocode:
template<typename ... Ts>
constexpr auto fill_and_gen_objects(Ts* ... os)
{
bool some_status = true; //whatever
return std::make_tuple(some_status, gen_object(os) ...);
}
Ok, actually it even compiles, see here
EDIT: downgraded to C++14 ... that's what you've tagged.
Same for C++17 using CTAD
template<typename ... Ts>
constexpr auto fill_and_gen_objects(Ts* ... os)
{
bool some_status = true; //whatever
return std::tuple{some_status, gen_object(os) ...};
}
Same for C++20 using abbreviated function template syntax
constexpr auto fill_and_gen_objects(auto* ... os)
{
bool some_status = true; //whatever
return std::tuple{some_status, gen_object(os) ...};
}
C++20 with indices by using integer sequence (untested):
constexpr auto fill_and_gen_objects(auto* ... os)
{
bool some_status = true; //whatever
return []<int ... I>(std::index_sequence<I...>, auto tup){ return std::tuple{some_status, gen_object(std::get<I>(tup)) ...};}
(std::make_index_sequence<sizeof...(os)>{}, std::tuple{os...})
}
Furthermore, here is the C++27 solution:
void do_my_fckng_work() { bool asap = true; }

How is it possible to generate an OR like statement compile time? C++

I defined a function and a type enum like this:
enum ActionType : int {
ACTION_FOO, ACTION_BAR, ACTION_BAZ
};
template<int T>
bool TestAction(std::string input, Result& result);
TestAction returns an indicator of success, and writes the output to the Result parameter. It is specialized for all ActionType values. I call it like this:
std::string input = "test";
Result res;
bool ok = TestAction<ACTION_FOO>(input, res) || TestAction<ACTION_BAR>(input, res) || TestAction<ACTION_BAZ>(input, res);
As soon as one call returns a true value, there is no need to call the other functions, so the || operator works well here.
The code works as intended, but I would like to avoid writing the OR statement by hand (or with macros), and instead generate a similar code compile time with templates. Is it possible somehow?
I can accept if I approach the problem from the wrong direction, but then please give me hints about how could I rework this code.
Here's how I would do it:
#include <utility>
template <typename T, T ...I, typename F>
bool static_any_of(F &&func, std::integer_sequence<T, I...>)
{
return (func(std::integral_constant<T, I>{}) || ...);
}
template <auto N, typename F>
bool static_any_of_n(F &&func)
{
return static_any_of(std::forward<F>(func), std::make_integer_sequence<decltype(N),N>{});
}
enum ActionType : int
{
ACTION_FOO, ACTION_BAR, ACTION_BAZ,
ActionType_Count,
};
int main()
{
bool ok = static_any_of_n<+ActionType_Count>([](auto index)
{
return TestAction<ActionType(index.value)>(/*...*/);
});
std::cout << ok;
}
We can do this by writing a second version of TestAction that can accept multiple actions!
// This is our base case
template<int T>
bool TestAction(std::string input, Result& result);
// This is the general case
template<int T, int T2, int... Ts>
bool TestAction(std::string input, Result& result) {
return TestAction<T>(input, result) || TestAction<T2, Ts...>(input, result);
}
Then, to use it, we just provide the actions:
std::string input = "test";
Result res;
bool ok = TestAction<ACTION_FOO, ACTION_BAR, ACTION_BAZ>(input, res);
If you're using C++17, we can also eliminate the recursion using a fold expression:
template<int... Ts>
bool TestActions(std::string input, Result& result) {
return (TestAction<Ts>(input, result) || ...);
}
Other recommendations: If you're not modifying input anywhere in TestAction, you can eliminate copying by declaring input as a const std::string& so that it's passed by reference

Why does if constexpr require an else to work?

I am trying to use if constexpr in the following way:
template<template <typename First, typename Second> class Trait,
typename First, typename Second, typename... Rest>
constexpr bool binaryTraitAre_impl()
{
if constexpr (sizeof... (Rest) == 0)
{
return Trait<First, Second>{}();
}
return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
}
Example use case:
static_assert(binaryTraitAre_impl<std::is_convertible,
int, int&,
int*, void*>());
But this fails to compile
clang:
error: no matching function for call to 'binaryTraitAre_impl'
return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gcc:
prog.cc: In instantiation of 'constexpr bool binaryTraitAre_impl() [with Trait = std::is_convertible; First = int*; Second = void*; Rest = {}]':
prog.cc:9:80: required from 'constexpr bool binaryTraitAre_impl() [with Trait = std::is_convertible; First = int; Second = int&; Rest = {int*, void*}]'
prog.cc:15:83: required from here
prog.cc:9:80: error: no matching function for call to 'binaryTraitAre_impl<template<class _From, class _To> struct std::is_convertible>()'
9 | return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
prog.cc:3:17: note: candidate: 'template<template<class First, class Second> class Trait, class First, class Second, class ... Rest> constexpr bool binaryTraitAre_impl()'
3 | constexpr bool binaryTraitAre_impl()
| ^~~~~~~~~~~~~~~~~~~
prog.cc:3:17: note: template argument deduction/substitution failed:
prog.cc:9:80: note: couldn't deduce template parameter 'First'
9 | return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
But I found the error goes away once I add else:
template<template <typename First, typename Second> class Trait,
typename First, typename Second, typename... Rest>
constexpr bool binaryTraitAre_impl()
{
if constexpr (sizeof... (Rest) == 0)
{
return Trait<First, Second>{}();
}
else
{
return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
}
}
live demo
What happened? Why can the compiler not infer the else in this case?
This is the excerpt from cppreference on constexpr if:
Constexpr If
The statement that begins with if constexpr is known as the constexpr if statement.
In a constexpr if statement, the value of condition must be a contextually converted constant expression of type bool. If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.
It is clear that only one of the two branches is discarded. In your case, the culprit code cannot be discarded because it's outside the else clause.
if constexpr when the clause is true doesn't eliminate code outside of the corresponding else block.
You could extend C++ to do that, but it quickly becomes a pain. Only the most trivial cases are obvious, and specifying what the trivial cases are is a pain. I mean do you cover:
if constexpr( blah ){
if (true) return 7;
}
? How about
if constexpr( blah ){
if (blah) return 7;
else exit(-1);
}
? Or
if constexpr( blah ){
if (blah) return 7;
else return false;
}
or
if constexpr( blah ){
if (blah) goto foo;
return false;
foo: return true;
}
or how about:
if constexpr( blah ){
std::size_t count = 0;
while (foo != 1 && (++count < (std::size_t)-1))
switch (foo%2) {
case 1: foo = 3*foo+1;
case 0: foo = foo/2;
}
}
if (count < (std::size_t)-1) return true;
}
? I can come up with a near continuum of cases that are slightly more or less "obvious" in their never-return. And the payoff? Not having an else. Lots of problems, little benefit.
Compilers have ad-hoc rules to detect unreachable code and the like. These don't have to be as formally specified as the standard, and they can differ from one compiler to another.
The standard, meanwhile, has to be the same for every compiler. And the rules for what is and isn't eliminated have to be identical.
The standard applies a simple rule; the if and else blocks are the only candidates for eliminatation.
So the standard doesn't do that. If you want code to be eliminated, put it in a if constexpr or else block of the if constexpr. Language development resources are better spent on things that have better yield and are less painful.

Partial template specialization for more than one typename

In the following code, I want to consider functions (Ops) that have void return to instead be considered to return true. The type Retval, and the return value of Op are always matching. I'm not able to discriminate using the type traits shown here, and attempts to create a partial template specialization based on Retval have failed due the presence of the other template variables, Op and Args.
How do I specialize only some variables in a template specialization without getting errors? Is there any other way to alter behaviour based on the return type of Op?
template <typename Retval, typename Op, typename... Args>
Retval single_op_wrapper(
Retval const failval,
char const *const opname,
Op const op,
Cpfs &cpfs,
Args... args) {
try {
CallContext callctx(cpfs, opname);
Retval retval;
if (std::is_same<bool, Retval>::value) {
(callctx.*op)(args...);
retval = true;
} else {
retval = (callctx.*op)(args...);
}
assert(retval != failval);
callctx.commit(cpfs);
return retval;
} catch (CpfsError const &exc) {
cpfs_errno_set(exc.fserrno);
LOGF(Info, "Failed with %s", cpfs_errno_str(exc.fserrno));
}
return failval;
}
You need an explicit specialization, not partial.
template <typename Retval, typename Op, typename... Args>
Retval single_op_wrapper(
Retval const failval,
char const *const opname,
Op const op,
Cpfs &cpfs,
Args... args) {
try {
CallContext callctx(cpfs, opname);
Retval retval;
if (std::is_same<bool, Retval>::value) {
(callctx.*op)(args...);
retval = true;
} else {
retval = (callctx.*op)(args...);
}
assert(retval != failval);
callctx.commit(cpfs);
return retval;
} catch (CpfsError const &exc) {
cpfs_errno_set(exc.fserrno);
LOGF(Info, "Failed with %s", cpfs_errno_str(exc.fserrno));
}
return failval;
}
template<typename Op, typename... Args> void single_op_wrapper<void, Op, Args>(...) {
...
}
Edit: Forgot you were writing a function, not a class.
Template functions cannot be partially specialized. There are different things that you can do: you can wrap the function into a class template with a single static method and specialize the class template, or you can use SFINAE to select the best choice of function among different template functions:
template <typename O, typename Args...>
void single_op_wrapper( /* all but failval */ ) { // [+]
// implementation for void
}
template <typename R, typename O, typename Args...>
typename boost::enable_if< boost::is_same<R,bool>, bool >::type // bool if condition is met
single_op_wrapper( /* all args */ ) {
// implementation for R being a bool
}
template <typename R, typename O, typename Args...>
typename boost::enable_if< boost::is_same<R,char> >::type // by default bool
single_op_wrapper( /* all args */ ) {
// implementation for void return
}
template <typename R, typename O, typename Args...>
typename boost::disable_if_c< boost::is_same<R,char>::value //[*]
|| boost::is_same<R,bool>::value
, R >::type
single_op_wrapper( /* all args */ ) {
// implementation when R is neither bool nor void
}
On the separate template for void [+]:
In C++ you cannot have a function that takes an argument of type void. That means that you cannot use the same arguments for the void case as you are using for the rest of them.
On the metaprogramming side:
There are a couple of tricky bits here... the enable_if is a metafunction that defines an internal type if the condition is met or nothing otherwise. When the compiler tries to substitute the types in the template, the return type will only be valid (and as such the function be a candidate) if the condition is met. The disable_if metafunction has the opposite behavior. The straight variant enable_if/ disable_if take a metafunction as first argument and optionally a type as second argument. The second version enable_if_c / disable_if_c take a boolean as first argument.
It is important in [*] to note that the functions must be exclusive. That is, if for a given type more than one of the templates are candidates, as none of them is an specialization of the others, the compiler will stop with an ambiguity error. That is the reason for using the disable_if in the last template.
Note: I have used boost namespace instead of std as I have not played ever with metaprogramming in c++0x, but I believe that you can change the boost namespace with std in your compiler. Check the docs in advance!
This is the trick I use to bypass the lack of partial template specialization for functions in C++. Basically this relies on a grey zone between function overloading and multiple function declarations that do not result in a compiler error thanks to SFINAE rules (all enable_if_t boolean conditions are mutually exclusive, hence only one declaration is valid given any specific compilation unit context)
template < class T, std::enable_if_t< sizeof(T) == 4, int > = 0 >
T do_transform(T inp) {
// 4 byte implementation
}
template < class T, std::enable_if_t< sizeof(T) == 8, int > = 0 >
T do_transform(T inp) {
// 8 byte implementation
}
template <class T, std::enable_if_t< sizeof(T) > 8, int> = 0 >
T do_transform(T inp) {
// very much wide word types
}