简体   繁体   中英

C++ how to emulate a function without implicit conversions?

I'm trying to use a function template (or some other templatized construction) to emulate a function without implicit conversions applied to either the arguments or the return expression. What's the most robust way to do this?

Ideally, I'm trying to come up with a way to do this that's easy to hide behind a template or a macro, has decent error messages, and doesn't impose a runtime penalty.

This is intended as a metaprogramming exercise; I'm not going to be doing this in production code.

The function below, func1 takes two longs and adds them together, performing conversions as usual.

long func1(long a, long b) {
  return a + b;
}

I'd like to define func in such a way that a and b can only be long s and check at compile time that the return expression really does have the indicated type without an implicit conversion being inserted.

For the sake of concreteness, I've tried to do this a couple of ways so far, and here they are.

One way of preventing conversions (for the arguments only) is to use a function template with a deleted implementation and a specialization for the combination of arguments.

template <class L1, class L2>
long func2(L1 a, L2 b) = delete;

template<>
long func2(long a, long b) {
  return a + b;
}

It's also possible to use enable_if to accomplish the same thing.

template <class L1, class L2>
std::enable_if_t<
  std::is_same<L1, long>::value && std::is_same<L2, long>::value
, long> func3(L1 a, L2 b) {
  return a + b;
}

However, I can't figure out how to incorporate an assertion about the return type without costing myself a copy or a move, or needing to do complex things to strip CV qualification and ref-ness.

// doesn't work, unnecessary copy
template <class L1, class L2>
std::enable_if_t<
  std::is_same<L1,long>::value && std::is_same<L2,long>::value,
long> func4(L1 a, L2 b) {
  auto out = a + b;
  static_assert(std::is_same<decltype(out), long>::value);
  return out;
}

I can, however, move the function body into a lambda and then use static_assert s to list all the conditions I want. I'm a little concerned that this approach does something surprising.

template <class L1, class L2>
long func5(L1 a, L2 b) {
  static auto wrapped = [&](){
    return a + b;
  };
  static_assert(std::is_same<decltype(a), long>::value);
  static_assert(std::is_same<decltype(b), long>::value);
  static_assert(std::is_same<decltype(wrapped()), long>::value);
  return wrapped();
}

There are probably a lot of ways to do this. Here's one. I don't think there's any reason to check all those cases. What do you want to do about const-volatile qualifiers? Allow or disallow?

#include <type_traits>
#include <iostream>

template<typename Long>
Long func1(Long a, Long b) {
    static_assert(std::is_same_v<Long, long>);
    return a + b;
}
int main()
{
    long a = 1;
    const long b = 2;
    const long &c = a;
    std::cout << func1(c,b) << std::endl;
}

No need of lambda:

template <class L1, class L2>
long func2(L1, L2) = delete;

decltype(auto) func2(long a, long b) {
  return a + b;
}

static_assert(std::is_same<long, decltype(func2(42L, std::declval<long>()))>::value);

No need to generate a temporary to test for its type, simply use decltype

template <class L1, class L2>
auto func3(L1 a, L2 b)
-> std::enable_if_t<
     std::is_same<L1, long>::value &&
     std::is_same<L2, long>::value &&
     std::is_same<decltype(a+b), long>::value, 
     long>
{
  return a + b;
}

or with static_assert :

template<typename T1, typename T2>
void func(T1, T2) = delete;

long func(long a, long b)
{
  static_assert(std::is_same<long,decltype(a+b)>::value,"!!");
  return a+b;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM