Conditionally enable non-template function c++
Asked Answered
D

3

6

Background

I'm working on a C++ project in High Performance Computing using MPI. I have a function with a few different overloads, that I use to convert different types into strings:

void append(std::string& s, int value);
void append(std::string& s, void* value);
void append(std::string& s, MPI_Request request); 

This worked fine while I was using Open MPI. In OpenMPI, MPI_Request is an alias for ompi_request_t*, so each overload has a different signature.

Recently, however, I tried compiling the code with MPICH. In MPICH, MPI_Request is an alias for int, and the result is that the above code fails to compile because append is defined for int twice:

/home/me/NimbleSM/src/mpi-buckets/src/mpi_err.hpp: At global scope:
/home/me/NimbleSM/src/mpi-buckets/src/mpi_err.hpp:28:6: error: redefinition of ‘void append(std::__cxx11::string&, int)’
 void append(std::string& s, int i) { s.append(std::to_string(i)); }
      ^~~
/home/me/NimbleSM/src/mpi-buckets/src/mpi_err.hpp:17:6: note: ‘void append(std::__cxx11::string&, MPI_Request)’ previously defined here
 void append(std::string& s, MPI_Request request)

Question

How should I write append(std::string&, MPI_Request) so that the compiler ignores it when MPI_Request is defined as an int, but recognizes it when MPI_Request is a library type?

Attempted solution: enable_if fails

I attempted to write a solution based on std::enable_if, where the function is only enabled if MPI_Request is a different type than int.

auto append(std::string& s, MPI_Request request)
    -> typename std::enable_if<!std::is_same<MPI_Request, int>::value, void>::type
{ 
    str(s, (void*)request); 
}

This fails because when MPI_Request is the same as int, the statement is always false, and since it's not dependent on any template parameters, the compiler flat-out refuses to compile it.

How do I fix this, and make append contingent on MPI_Request being different from int?

Delve answered 21/5, 2019 at 21:8 Comment(6)
Will you want the same behavior as the int overload if MPI_Request is an int?Md
There is no way to emulate SFINAE with non-templates. The main usage point of SFINAE is to avoid instantiating the functions with types which can't be used in functions bodies. Non-templates will not allow you this.Itin
I want the compiler to ignore the function if MPI_Request is an int. So it just pretends the MPI_Request overload isn't thereDelve
maybe there is a design problem to fix at a higher level as what ever you do, you will never be able to distinguish void append(std::string& s, int value) from void append(std::string& s,int mpi_request) (same signature)Resurge
I don't need to distinguish between them; I just need there to be an overload if MPI_Request isn't an int. If it is an int, I'm fine with the int version being called (I just need the non-int version to not exist)Delve
@chris: IMO, your answer is fine (and it is my favorite if we cannot use preprocessor macro).Ravo
S
2

That's unfortunate. The bottom line is that enable_if can only be used inside SFINAE context which requires the T - template. To iterate on your idea we can specify our requirements in the return type so that the template matches only MPI_Request and only if the MPI_Request type is not an int.

#include <string>
#include <type_traits>
using MPI_Request = int;// Or something else

template<typename T>
using T_is_MPI_and_not_also_int = std::conjunction<std::is_same<T,MPI_Request>, std::negation<std::is_same<MPI_Request,int>>>;

template<typename T>
std::enable_if_t<T_is_MPI_and_not_also_int<T>::value,void> 
append(std::string& s, T request);

Full example, you can even see which cout line gets inlined into main.

Slovak answered 21/5, 2019 at 21:25 Comment(0)
A
2

You can make it a function template with defaulted template parameter, which allows SFINAE to work:

void append(std::string& s, int value);
void append(std::string& s, void* value);

template <typename MPI_Request_ = MPI_Request,
    typename std::enable_if<
        !std::is_same<MPI_Request_, int>::value
        && std::is_same<MPI_Request_, MPI_Request>::value
    , int>::type = 0>
void append(std::string& s, MPI_Request_ request)
{ 
    str(s, (void*)request);
    // Or, if you want the implementation in the source file,
    // call another function like: append_mpi_request(s, request);
}

Demo

Armyn answered 21/5, 2019 at 21:30 Comment(1)
Your answer is cleaner than mine.Slovak
V
0

Here's a low tech solution. Use a preprocessor macro.

void append(std::string& s, int value);
void append(std::string& s, void* value);

#ifdef MPI_REQUEST_IS_INT
// No need to declare append() for MPI_Request
#else
void append(std::string& s, MPI_Request request); 
#endif

Use the same strategy in the .cpp file.

Vaticide answered 21/5, 2019 at 21:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.