7
\$\begingroup\$

For many years, I have waited for function aliases in C++. They still aren't here. I've been using macros to generate wrapper functions. This is my best attempt at the perfect function alias:

#define FUN_ALIAS(NEW_NAME, ...)                                                \
  template <typename... Args>                                                   \
  inline decltype(auto) NEW_NAME(Args &&... args)                               \
    noexcept(noexcept(__VA_ARGS__(std::forward<Args>(args)...))) {              \
    return __VA_ARGS__(std::forward<Args>(args)...);                            \
  }

I'm using std::forward to forward the arguments and I'm using decltype(auto) to get the right return type. In the future, we might have noexcept(auto) which will be perfect for this situation but I'm not getting my hopes up.

Here's an example from my OpenGL wrapper:

template <GLenum TYPE>
Shader<TYPE> makeShader();
template <GLenum TYPE>
Shader<TYPE> makeShader(const GLchar *, size_t);
template <GLenum TYPE, size_t... SIZES>
Shader<TYPE> makeShader(const GLchar (&... sources)[SIZES]);

FUN_ALIAS(makeVertShader, makeShader<GL_VERTEX_SHADER>)
FUN_ALIAS(makeFragShader, makeShader<GL_FRAGMENT_SHADER>)

What I want to know:

  • Is there a situation where the compiler will not optimize away the wrapper because the wrapper and the original function have different semantics?
  • Does this fail to compile for some functions?
\$\endgroup\$
  • \$\begingroup\$ I don't like macros, however this looks really nice! I think this will work unless your alias needs to be a template as well. Why do you need FUN_ALIAS_PTR? Doesn't the other cover it as well? \$\endgroup\$ – JVApen Aug 30 '18 at 6:20
  • \$\begingroup\$ @JVApen I actually don't need FUN_ALIAS_PTR! I'm trying to think of a reason why it should exist but I've come up short. I think I'll remove it. \$\endgroup\$ – Kerndog73 Aug 30 '18 at 6:46
  • \$\begingroup\$ Did you consider wrapper structs? Dumb example typed on mobile: ideone.com/iHGUkR \$\endgroup\$ – Daniel Jour Aug 31 '18 at 21:09
  • \$\begingroup\$ @DanielJour The problem with it is that it seems to be just storing a function pointer. Your example seems to be equivalent to constexpr auto NEW_NAME = OLD_NAME; It doesn't support overloaded functions. In fact, that's what the discussion about FUN_ALIAS_PTR is about. \$\endgroup\$ – Kerndog73 Aug 31 '18 at 23:36
  • \$\begingroup\$ And why not simply #define makeVertShader makeShader<GL_VERTEX_SHADER>? \$\endgroup\$ – bipll Sep 1 '18 at 7:25
7
\$\begingroup\$

Is there a situation where the compiler will not optimize away the wrapper because the wrapper and the original function have different semantics?

Yes to the first part and no to the second. Any optimizing compiler will inline your wrapper, as it consists of only one return statement.

But on the other hand, the original function might have different semantics: It might be constexpr for example, and your alias will not be.

Does this fail to compile for some functions?

No (except for the constexpr part).

So here are some improvements you can use:

  1. Templates are implicitly inline, so you don't need to write it explicitly.

  2. Use a lambda to combat the missing constexprness of the alias, because since C++17 lambdas are implicitly constexpr if they can be.

This will get you:

#define FUN_ALIAS(NEW_NAME, ...)                                                \
  inline auto NEW_NAME = [](auto &&... args)                                   \
    noexcept(noexcept(__VA_ARGS__(std::forward<decltype(args)>(args)...)))     \
    -> decltype(auto) {                                                        \
    return __VA_ARGS__(std::forward<decltype(args)>(args)...);                \
  };
\$\endgroup\$
  • 1
    \$\begingroup\$ I was using __VA_ARGS__ so that it wouldn't choke up on template parameter lists likeFUN_ALIAS_WRAP(bar, foo<int, long>) \$\endgroup\$ – Kerndog73 Aug 30 '18 at 8:22
  • 1
    \$\begingroup\$ I like the idea of the lambda. Will that automatically be constexpr if the call is constexpr? \$\endgroup\$ – Kerndog73 Aug 30 '18 at 8:23
  • 2
    \$\begingroup\$ @Kerndog73 You guessed it :) \$\endgroup\$ – Rakete1111 Aug 30 '18 at 8:23
4
\$\begingroup\$

This function alias is deficient. decltype(auto) will get you the right return type, but it is not SFINAE friendly. For example, the following fails to compile:

struct A{};
struct B{};

bool old_name(A i)
{
    return false;
}

FUN_ALIAS(new_name, old_name);

// This does compile if I use old_name instead
template <typename T>
auto something_else(T t)
    -> decltype(new_name(t))
{
    return new_name(t);
}

// If the prior overload SFINAEs away, this overload is chosen
bool something_else(...)
{
    return true;
}

bool foo()
{
    // Tries the first overload (varargs are lowest priority), but
    // it's a hard error instead of SFINAE, because new_name isn't
    // SFINAE friendly
    return something_else(B{});
}

Instead of decltype(auto), you need to use a trailing return type:

#define FUN_ALIAS(NEW_NAME, ...)                                                \
  template <typename... Args>                                                   \
  inline auto NEW_NAME(Args &&... args)                                         \
    noexcept(noexcept(__VA_ARGS__(std::forward<Args>(args)...)))                \
    -> decltype(__VA_ARGS__(std::forward<Args>(args)...))                       \
  {                                                                             \
    return __VA_ARGS__(std::forward<Args>(args)...);                            \
  }

This allows the prior snippet to compile.

\$\endgroup\$
  • \$\begingroup\$ This is precisely what I was asking for. My exact words were “Does this fail to compile for some functions?” and this is exactly what I meant. \$\endgroup\$ – Kerndog73 Sep 5 '18 at 22:15
1
\$\begingroup\$

After taking suggestions from the answers, I have written this new and improved function alias macro:

#define FUN_ALIAS_SPEC(SPECS, NEW_NAME, ...)                                    \
  template <typename... Args>                                                   \
  SPECS auto NEW_NAME(Args &&... args)                                          \
    noexcept(noexcept(__VA_ARGS__(std::forward<Args>(args)...)))                \
    -> decltype(__VA_ARGS__(std::forward<Args>(args)...)) {                     \
    return __VA_ARGS__(std::forward<Args>(args)...);                            \
  }

#define FUN_ALIAS(NEW_NAME, ...)                                                \
  FUN_ALIAS_SPEC(inline, NEW_NAME, __VA_ARGS__)

Thanks to @Justin, this is now SFINAE friendly by putting the function call expression in decltype() rather than using decltype(auto).

Thanks to @Rakete1111, this is now constexpr friendly by allowing the user to put specifiers before the definition. This means that the alias can also be made into static class member.

Thank you both for participating!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.