有没有一种方法可以在传递左值时指定不同的函数,而在不传递左值时指定另一个函数?

Is there a way to specify a different function when passed a lvalue and another when not?

如果我使用的术语不正确,请提前致歉。

我想要 2 个函数 同名 ,一个在值是左值时调用,另一个在不是左值时调用。示例:

typedef void (*fn_t)();
void fn() { }

fn_t pFn = fn;

fn_t* distinguish(fn_t pFn)
{
  return pFn;
}

// NOTE: Will not work
fn_t*& distinguish(fn_t& pFn)
{
  return pFn;
}

fn_t*& distinguish1(fn_t& pFn)
{
  return pFn;
}

int main()
{
  // call the one that takes a parameter from which I cannot get the address of the function pointer
  distinguish(fn);

  fn_t pFn = fn;
// call the one that takes a parameter from which I can get the address of the function pointer
  distinguish(pFn);
  distinguish1(pFn); // this will work
  distinguish1(fn); // this will not work
  return 0;
}

这可能吗?也许我可以使用 SFINAE?

我正在尝试做什么:

我走弯路,发现他们用的cast很丑,所以我写了几个模板函数帮我做cast。

// Cast a function pointer to a void *
template <typename RET_TYPE, typename...ARGs>
void* fnPtrToVoidPtr(RET_TYPE(WINAPI * pOriginalFunction)(ARGs...))
{
    return (void*)pOriginalFunction;
}

// Cast a function pointer that is referencable to a void *&
template <typename RET_TYPE, typename...ARGs>
void*& fnPtrRefToVoidPtrRef(RET_TYPE(WINAPI*& pOriginalFunction)(ARGs...))
{
    return (void*&)pOriginalFunction;
}

这允许我进行以下调用:

BOOL (WINAPI *pDestroyIcon)(HICON) = DestroyIcon;
DetourAttach(&fnPtrRefToVoidPtrRef(pDestroyIcon), fnPtrToVoidPtr(DestroyIcon));

但是,我想知道是否可以将两个函数名称 fnPtrRefToVoidPtrReffnPtrToVoidPtr 合并为一个名称。

执行以下操作无效,因为它无法推断模板参数:

// Cast a function pointer to a void *
template <typename RET_TYPE, typename...ARGs>
void* fnPtrToVoidPtr(RET_TYPE(WINAPI * & pOriginalFunction)(ARGs...))
{
    return (void*)pOriginalFunction;
}

// Cast a function pointer that is referencable to a void *&
template <typename RET_TYPE, typename...ARGs>
void*& fnPtrToVoidPtr(RET_TYPE(WINAPI * && pOriginalFunction)(ARGs...))
{
    return (void*&)pOriginalFunction;
}

BOOL (WINAPI *pDestroyIcon)(HICON) = DestroyIcon;
void* p1 = fnPtrToVoidPtr(DestroyIcon);
void** p2 = fnPtrToVoidPtr(pDestroyIcon);

但这会导致以下错误:

// error C2784: 'void *&`anonymous-namespace'::fnPtrToVoidPtr(RET_TYPE (__stdcall *&&)(ARGs...))' : could not deduce template argument for 'overloaded function type' from 'overloaded function type'

使用我原来的函数,这个工作正常:

    BOOL (WINAPI *pDestroyIcon)(HICON) = DestroyIcon;
    void* p1 = fnPtrToVoidPtr(DestroyIcon);
    void** p2 = &fnPtrRefToVoidPtrRef(pDestroyIcon);

编辑: 此处不需要 std::forward,但它只是为了展示更多 "transparent" 值传递函数的视图。

需要时使用 & 和 &&,其中 & = 左值,&& = 右值。

http://coliru.stacked-crooked.com/a/81539ac10b8be5a1

#include <iostream>
#include <utility>

int& forward(int& i)
{
    std::cout << "lvalue: ";
    return std::forward<int&>(i);   
}

int&& forward(int&& i)
{
    std::cout << "rvalue: ";
    return std::forward<int&&>(i);    
}

int*&& forward(int*&& ptr_i)
{
    std::cout << "rvalue ptr: ";
    return std::forward<int*&&>(ptr_i);   
}

int*& forward(int*& ptr_i)
{
    std::cout << "lvalue ptr: ";
    return std::forward<int*&>(ptr_i);   
}

const int* forward(const int* const  ptr_to_const)
{
    std::cout << "const ptr to lvalue: ";
    return std::forward<const int* const>(ptr_to_const);
}

int main()
{
    int i = 1;

    const int j = 13;

    int* ptr_i = &i;

    const int * const  const_ptr_const_i = &j;

    int * const const_ptr_i = &i;


    std::cout << forward(0) << std::endl;
    std::cout << forward(i) << std::endl;
    std::cout << forward(ptr_i) << std::endl;
    std::cout << forward(&i) << std::endl;

    //These two are almost the same by nature; note the variables they point to are different.
    std::cout << forward(const_ptr_const_i) << std::endl;
    std::cout << forward(const_ptr_i) << std::endl;

    std::cout << "Cannot have a const pointer to a const rvalue because you can't really have a const pointer to a temporary/disappearing/ephermal rvalue." << std::endl;


    return 0;
}

这里唯一的问题是您在使用 typedef 时搞乱了间接寻址的数量。我已经在 this associated sample.

中解决了这个问题

总之,之所以不行,是因为你拿了一个fn_t*&。值 pFn 只是一个 fn_t,而不是 fn_t*。这就是引用不能引用它的原因。由于 fn_t 已经包含指针部分,因此您不需要添加额外的指针。

// Cast a function pointer to a void *
template <typename RET_TYPE, typename...ARGs>
void* fnPtrToVoidPtr(RET_TYPE(WINAPI *&& pOriginalFunction)(ARGs...)) {
  return (void*)pOriginalFunction;
}
// Cast a function pointer that is referencable to a void *&
template <typename RET_TYPE, typename...ARGs>
void*& fnPtrToVoidPtr(RET_TYPE(WINAPI*& pOriginalFunction)(ARGs...)) {
  return (void*&)pOriginalFunction;
}

是我想你想要的。右值得到 && 左值 & 并且没有检测到歧义。

现在上面的缺点是 && 案例无法正确推断签名。我可以想出办法,或者我可以使用蛮力来避免这个问题。我们将两者移动到一个 details 命名空间中,我们添加了一个标记类型以在两者之间进行调度,我们删除了 &&,然后我们添加了一个调度函数,如下所示:

namespace details {
  template <typename RET_TYPE, typename...ARGs>
  void* fnPtrToVoidPtr(std::true_type, RET_TYPE(WINAPI * pOriginalFunction)(ARGs...)) {
    return (void*)pOriginalFunction;
  }
  // Cast a function pointer that is referencable to a void *&
  template <typename RET_TYPE, typename...ARGs>
  void*& fnPtrToVoidPtr(std::false_type, RET_TYPE(WINAPI*& pOriginalFunction)(ARGs...)) {
    return (void*&)pOriginalFunction;
  }

}
template<class T>
auto fnPtrToVoidPtr(T&& t)
-> decltype(
  details::fnPtrToVoidPtr(
    std::is_rvalue_reference<T&&>{}, std::forward<T>(t)
  )
) {
  return
  details::fnPtrToVoidPtr(
    std::is_rvalue_reference<T&&>{}, std::forward<T>(t)
  );
}

嗯,真是一团糟。

如果 foo 是一个函数名,你需要传入 &foo 才能使上面的代码生效。如果你只传入 foo,它会编译失败。如果需要,您可以编写一个将 R(&)(Args...) 映射到 R(*)(Args...) 的适配器,或者以其他方式解决该问题。

live example