6

The following compiles on Visual Studio:

template<typename ArgType, typename ReturnType>
struct Test
{
    using FunctionPointerType = std::conditional_t<
        std::is_same_v<ArgType, void>
        , ReturnType(*)()
        , ReturnType(*)(ArgType)
    >;
    FunctionPointerType Func;
};

int main()
{
    Test<void, char> tt;
}

But does not compile on Linux g++. The error I get is

error : invalid parameter type ‘void’

I know I cannot use void in templates, which is why I have used std::conditional_t and std::is_same_v.

I cannot see what is incorrect, can someone please tell me?

JeJo
  • 30,635
  • 6
  • 49
  • 88
Wad
  • 1,454
  • 1
  • 16
  • 33
  • "I know I cannot use void in templates" Says who? – Ben Voigt May 25 '22 at 16:29
  • Anyway, in your specific case using a partial specialization would be much simpler. – Ben Voigt May 25 '22 at 16:30
  • 4
    A specialization may also be necessary, since when instantiating `std::conditional_t`, both the type `T` for if `B` is `true` and the type `F` for if `B` is `false` must be valid. Since the problem is that `char(*)(void)` is invalid and you want `char*()`, `conditional_t` isn't the appropriate tool here. – Nathan Pierson May 25 '22 at 16:32
  • @BenVoigt It looks like the problem comes up in [this question](https://stackoverflow.com/questions/22249882/invalid-void-parameter-when-combining-template-parameters-to-form-a-function-sig), where the function signature you'd get by declaring `char foo(void);` isn't the same as what you'd get by performing the substitution with `ArgType = void` – Nathan Pierson May 25 '22 at 16:33
  • Simplified with working but strange text case - live - https://godbolt.org/z/TT1qfdW85 – Richard Critten May 25 '22 at 16:44

3 Answers3

2

Not really an answer to your question (Why doesn't my code work), but this works:

template<typename ArgType, typename ReturnType>
struct Test
{
  using FunctionPointerType = ReturnType(*)(ArgType);
  FunctionPointerType Func;
};

template<typename ReturnType>
struct Test<void, ReturnType>
{
  using FunctionPointerType = ReturnType(*)();
  FunctionPointerType Func;
};
Marshall Clow
  • 15,972
  • 2
  • 29
  • 45
2

In std::conditional_t<B, T, F> both the true and false specialization should have valid types T as well as F. In your case, since the F deduce to an invalid char(*)(void) type, std::conditional can not be used here.

I would suggest a helper traits function_ptr_t as alternative

#include <type_traits>

template<typename RetType, typename... Args> struct function_ptr final {
    using type = RetType(*)(Args...);
};

template <typename RetType> struct function_ptr<RetType, void> final {
    using type = RetType(*)();
};
// alias helper
template<typename RetType, typename... Args>
using function_ptr_t = typename function_ptr<RetType, Args...>::type;


template<typename RetType, typename... Args>
struct Test
{
     function_ptr_t<RetType, Args...> Func;
};

See a demo


Note that, I have swapped the template arguments in the class template Test, and made the second template argument be a variadic, so that the Test<char> will result in a member function pointer of type char(*)().

That means the following will work:

Test<char> tt1;
static_assert(std::is_same_v<char(*)(), decltype(tt1.Func)>, " are not same!");

Test<char, void> tt2;
static_assert(std::is_same_v<char(*)(), decltype(tt2.Func)>, " are not same!");

Test<void, double> tt3;
static_assert(std::is_same_v<void(*)(double), decltype(tt3.Func)>, " are not same!");

If that is not, you wanted, substitute the variadic template arguments to be a normal one.

JeJo
  • 30,635
  • 6
  • 49
  • 88
1
template<typename ArgType, typename ReturnType>
struct Test
{
  using FunctionPointerType = std::conditional_t<std::is_same_v<ArgType, void>, ReturnType(*)(), ReturnType(*)(ArgType)>;
  FunctionPointerType Func;
};

conditional_t takes 3 arguments. A bool and 2 types.

The right type, when ArgType is void, is ReturnType(*)(void) -- not a legal type.

The error happens immediately.

MSVC has a bad habit of treating templates like macros.

Something like this:

template<class...Args>
struct Apply {
  template<template<class...>class Z>
  using To = Z<Args...>;
};

template<class R, class...Args>
using FunctionPtr = R(*)(Args...);

template<typename ArgType, typename ReturnType>
struct Test
{
  using FunctionPointerType =
    std::conditional_t<
      std::is_same_v<ArgType, void>,
      Apply<ReturnType>,
      Apply<ReturnType, ArgType>
    >::template To<FunctionPtr>;
  FunctionPointerType Func;
};

builds 2 pack appliers, then applies one to FunctionPtr depending on which one is picked.

Here, I avoid forming X(void) types until after the condition branch has occurred.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524