C++将序列上限传递给可变参数函数模板

问题描述:

我在模板的帮助下以特定方式调用API,并且传递一个常量参数时会留下一个问题。C++将序列上限传递给可变参数函数模板

我尝试用INT界

template <typename F, typename ...Ts> 
      static int f3(int bound, CString file, int line, CString Caller, 
         CString f_name, F f, Ts&& ...ts) { 
       int err = fn(bound, file, line, Caller, f_name, 
       f, std::tuple<Ts...>(ts...), seq3<bound>{}, // error C2975 
       seq1<sizeof...(Ts)>{});     
       return err; 
    } 

在主:

int const bound; 
bound = 4; 

err = fn(bound, api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path"); 

compiler error C2975: 'N': invalid template argument for 'seq3', expected compile-time constant expression 

如何解决这一问题?

我的解决方法由现在:

ERR = F3(API(GetModuleFileName),rval中,nullptr,路径,MAX_PATH,L “EXE路径”);

f3是一个具有3个参数的API的专门化,因为我到现在还不能通过上限 - 在这种情况下是4 - 用于生成序列:< 1,2,3>。 需要使用3个参数来调用API,其中tupel从f3()中的参数rval开始。

背景:

API是使用#define

F3调用API。

f3在序列/ tupel的0位置处理API的返回值。

f3使用所有参数调用另一个可变参数函数来记录调试信息。

一个tupel和两个函数调用的序列。

问题:

我想通过一个参数来控制上限不是由三元组大小,而是由API函数签名给定的序列组成。我想只有一个fn()对于所有的API而不是f0(),f1(),f2(),f3().....对于有0,1,2,3 ...参数的API 。

我想是这样的:

ERR = FN(SEQ3 < 4>,API(GetModuleFileName),RVAL,nullptr,路径,MAX_PATH,L “EXE路径”)

这是我的工作代码:

#include <windows.h> 
#include <atlstr.h> 
#include <tuple> 
#include <utility> 

template <int ... Ns> struct seq_3 {};         
template <int ... Ns> struct seq3_n {}; 

template <int I, int ... Ns> struct seq3_n<I, Ns...>{ 
    using type = typename seq3_n<I - 1, I - 1, Ns...>::type;}; 

template <int ... Ns> struct seq3_n<1, Ns...>{ 
// skip first argument : rval, because it doesn't fit to API, 
// but needed for calling other function  
    using type = seq_3<Ns...>;    }; 

template <int N> 
    using seq3 = typename seq3_n<N>::type; 

template <int ... Ms> struct seq_1 {};       
template <int ... Ms> struct seq1_n {};  
template <int J, int ... Ms> struct seq1_n<J, Ms...>{ 
    using type = typename seq1_n<J - 1, J - 1, Ms...>::type; };  
template <int ... Ms> struct seq1_n<0, Ms...> { 
    using type = seq_1<Ms...>;    }; 
template <int M> 
    using seq1 = typename seq1_n<M>::type;  

template <typename F, typename TUP, int ... INDICES3, int ... INDICES1>       
    static int fn(CString file, int line, CString Caller, CString f_name, 
       F f, TUP tup, seq_3<INDICES3...>, seq_1<INDICES1...>) { 
       int err = 0; 
       // handling of rval = first element of tuple 
       std::get<0>(tup) = f(std::get<INDICES3>(tup) ...); // calling API 
       err = GetLastError(); 
       /* calling next function (variadic too) with same tupel, but other sequence 
       myOpenDebugOutputString(project, file, line, Caller, f_name, std::get<INDICES1>(tup) ..., "stop"); 
       */ 
       return err; } 

template <typename F, typename ...Ts> 
    static int f3(CString file, int line, CString Caller, CString f_name, 
       F f, Ts&& ...ts) { 
       int err = fn(file, line, Caller, f_name, 
       f, std::tuple<Ts...>(ts...), seq3<4>{}, // sequence fixed by f3 
       seq1<sizeof...(Ts)>{});     // 3 arguments api + skip 1 rval = 4 
       return err;        // given by signature of API 
} 


int main() {  
    // for calling simple API GetModulFileName with 3 arguments  
    //          returns len(path) 
    wchar_t  path[MAX_PATH];  
    DWORD   rval = 0; 
    int   err = 0; 
    rval = GetModuleFileName(nullptr, path, MAX_PATH);  
    err = GetLastError(); 

#define api(a) __FILE__, __LINE__, __func__, L#a, a 
// L#a becomes L"GetModuleFileName" 

    err = f3(api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path"); 

    return 0; } 

在此先感谢。

P.S. 我使用Microsoft Visual Studio 2015年

更新:

我试图从理查德·霍奇斯解决方案模板api_call以下。

std::tuple<GivenArgs...> tup(args...); 

// OK, but only for an api with 3 arguments 
callsite.function(std::get<0>(tup), std::get<1>(tup), std::get<2>(tup)); 

// compiler error too many arguments 
callsite.function(std::forward<GivenArgs>(args)..., seq1<callsite.nofArgs()>{}); 

// compiler error too few arguments 
callsite.function(tup, seq1<callsite.nofArgs()>{}); 

备注:

SEQ1 < 3> = seq_1 < 0,1,2>

callsite.nofArg()= 3

如何获得正确数量的参数呢?

+1

你究竟在做什么?将呼叫记录到API,包括有关呼叫站点的信息? –

+0

^那。我也不明白这个问题,但是如果你有任何错误信息,请将它们写入。 –

+0

是的,我试图以格式化的方式记录调用网站信息和许多其他变量。 – CarpeDiemKopi

这并不完全清楚你想如何处理错误等我假设返回一个错误代码和值的元组。

这是一个普遍的模式,我认为它会做你想做的。您需要小心emit_log的特殊化和重载,特别是在字节数组可能不是空终止或包含非打印字符的情况下。

为了方便起见,我使用了窄字符,但是这个想法可以通过一些编辑来处理宽字符。

注:在linux上编辑gcc所以我模拟了windows API。

#include <cstdint> 
#include <utility> 
#include <iostream> 
#include <variant> 

#define WINAPI 
#define _In_opt_ 
#define _Out_ 
#define _In_ 

struct _hmodule {}; 
using HMODULE = _hmodule*; 
using LPTSTR = char*; 
using LPCTSTR = const char*; 
using DWORD = std::uint32_t; 

extern DWORD WINAPI GetModuleFileName(
    _In_opt_ HMODULE hModule, 
    _Out_ LPTSTR lpFilename, 
    _In_  DWORD nSize 
); 

extern WINAPI DWORD GetLastError(); 

template<class Ret, class...Args> 
struct api_call_site 
{ 
    const char* file; 
    int line; 
    const char* current_function; 
    const char* called_function; 
    Ret (* function)(Args...); 
}; 

template<class Ret, class...Args> 
auto make_api_call_site(const char* file, int line, const char* callername, const char* calleename, Ret (* WINAPI callee)(Args...)) 
{ 
    return api_call_site<Ret, Args...> 
    { 
     file, 
     line, 
     callername, 
     calleename, 
     callee 
    }; 
} 

template<class T> 
void emit_log(LPCTSTR& sep, std::ostream& os, T&& x) 
{ 
    os << sep << x; 
    sep = ","; 
} 

template<class Ret> 
struct error_with_value 
{ 
    DWORD error; 
    Ret value; 

    bool has_error() const { return error != 0; } 
    friend std::ostream& operator<<(std::ostream& os, const error_with_value& ewv) 
    { 
     os << "{ error: " << ewv.error << ", value: "; 
     LPCTSTR sep = ""; 
     emit_log(sep, os, ewv.value); 
     os << " }"; 
     return os; 
    } 
}; 


#define api(a) make_api_call_site(__FILE__, __LINE__, __func__, #a, a) 


// this will need some specialisations... 
void emit_log(LPCTSTR& sep, std::ostream& os, std::nullptr_t) 
{ 
    os << sep << "nullptr"; 
    sep = ","; 
} 

template<class Ret, class...Args, class...GivenArgs> 
auto api_call(api_call_site<Ret, Args...> const& callsite, GivenArgs&&...args) -> error_with_value<Ret> 
{ 
    // log call here 
    std::clog << callsite.file << ":" << callsite.line << "@" << callsite.current_function << " - "; 
    std::clog << "calling " << callsite.called_function << "("; 
    // appropriate code to print arguments in a safe way here... 
    LPCTSTR sep = ""; 
    using expand = int[]; 
    void(expand{0, 
     (emit_log(sep, std::clog, args),0)... 
    }); 
    std::clog << ")"; 
    error_with_value<Ret> result 
    { 
     0, 
     callsite.function(std::forward<GivenArgs>(args)...) 
    }; 
    result.error = GetLastError(); 

    std::clog << " -> returns: " << result; 
    return result; 
} 

int main() 
{ 
    char buffer[255]; 
    DWORD bufsize = 255; 

    auto result = api_call(api(GetModuleFileName), nullptr, buffer, bufsize); 
    if (! result.has_error()) 
    { 
     // 
    } 

} 

输出示例:

main.cpp:[email protected] - calling GetModuleFileName(nullptr,,255) -> returns: { error: 0, value: 14 } 

http://coliru.stacked-crooked.com/a/e5da55af212d5500

如何获得的参数在API调用多少?

template<class Ret, class...Args> 
struct api_call_site 
{ 
    const char* file; 
    int line; 
    const char* current_function; 
    const char* called_function; 
    Ret (* function)(Args...); 

    // like this 
    static constexpr std::size_t nofArgs() 
    { 
     return sizeof...(Args); 
    } 
}; 
+0

非常感谢。我已经更改为宽字符,试图使用原始的Windows API GetModuleFilename并得到编译器错误:错误C2672:'make_api_call_site':没有匹配的重载函数发现错误C2784:'auto make_api_call_site(const char *,int,const char *,const char *,Ret(__cdecl *)(Args ...))':无法从'DWORD(HMODULE,LPWSTR,DWORD)'推导'Ret(__cdecl *)(Args ...)'的模板参数!如何解决这个问题? – CarpeDiemKopi

+0

@CarpeDiemKopi你需要检查日志记录功能等字符串的宽度。 –

+0

我摆脱了编译器错误。问题是使用WINAPI:我改变了Ret(* function)(Args ...); Ret(WINAPI *函数)(Args ...); \t和Ret(* WINAPI callee)(Args ...))到Ret(WINAPI * callee)(Args ...))。由于将WINAPI定义为Windows api仿真没有任何作用,因此以前工作得很好。 – CarpeDiemKopi

感谢理查德·霍奇斯,我可以解决我的问题:

现在有每一个API一个api_call和api_call背后的模板确定调用的API的签名。比我所要求的要好得多。

#include <windows.h> 
#include <atlstr.h> 
#include <tuple> 

template <int ... Ns> struct seq_3 {}; 
template <int ... Ns> struct seq3_n {}; 
template <int I, int ... Ns> struct seq3_n<I, Ns...> { 
    using type = typename seq3_n<I - 1, I - 1, Ns...>::type; 
}; 
template <int ... Ns> struct seq3_n<1, Ns...> { 
    // this sequence is more complicated in my real code, because 
    // there are more variables for logging, but not for api calling  
    using type = seq_3<Ns...>; 
}; 
template <int N> 
using seq3 = typename seq3_n<N>::type; 

template <int ... Ms> struct seq_1 {}; 
template <int ... Ms> struct seq1_n {}; 
template <int J, int ... Ms> struct seq1_n<J, Ms...> { 
    using type = typename seq1_n<J - 1, J - 1, Ms...>::type; 
}; 
template <int ... Ms> struct seq1_n<0, Ms...> { 
    using type = seq_1<Ms...>; 
}; 
template <int M> 
using seq1 = typename seq1_n<M>::type; 

// according to the solution from Richard Hodges 
// ********************************************* 
template<typename Ret, typename...Args> 
struct api_call_site 
{ 
    const CString file; 
    int line; 
    const CString Caller; 
    const CString f_name; 
    Ret(WINAPI* function)(Args...);  

    static constexpr std::size_t nofArgs() { 
     return sizeof...(Args); 
    } 
}; 

template<typename Ret, typename...Args> 
auto make_api_call_site(const CString file, int line, const CString Caller, const CString f_name, Ret(WINAPI* callee)(Args...)) 
// WINAPI see also here https://stackoverflow.com/questions/18912931/why-need-to-use-winapi-for-the-syntax-for-declaring-function-pointers-for-fun 
{ 
    return api_call_site<Ret, Args...> 
    { 
     file, 
      line, 
      Caller, 
      f_name, 
      callee 
    }; 
} 

template <typename Ret, typename...Args, typename TUP, int...INDICES3, int...INDICES1> 
int fn(api_call_site<Ret, Args...> const& callsite, TUP tup, seq_3<INDICES3...>, seq_1<INDICES1...>) { 
    int err = 0; 
    // handling of return value from api call goes always in position 0 from tuple 
    std::get<0>(tup) = callsite.function(std::get<INDICES3>(tup) ...); 
    err = GetLastError(); 
    /* calling next function (variadic too) with same tupel, but other sequence 
    myOpenDebugOutputString(project, file, line, Caller, f_name, std::get<INDICES1>(tup) ..., "stop"); 
    */ 
    return err; 
} 

template<typename Ret, typename...Args, typename...GivenArgs> 
int api_call(api_call_site<Ret, Args...> const& callsite, GivenArgs&&...args) 
{ 
    int err; 
    err = fn(callsite, std::tuple<GivenArgs...>(args...), seq3 <callsite.nofArgs()+1> {}, seq1 <sizeof...(GivenArgs)> {}); 
    return err; 
} 

int main() { 
    DWORD  size_path = 20; // make it small and get error 122 
    wchar_t  path[MAX_PATH]; // ERROR_INSUFFICIENT_BUFFER 
    DWORD  rval = 0; 
    int   err = 0; 
    CString  tolog1(L"EXE-Path determined"); 
    int   tolog2 = 25; 
    // old way without logging information 
    rval = GetModuleFileName(nullptr, path, MAX_PATH); 
    err = GetLastError(); 

    // new way with logging any variables ... behind the must variables for the api 
    // **************************************************************************** 
#define api(a) make_api_call_site(__FILE__, __LINE__, __func__, L#a, a) 
    err = api_call(api(GetModuleFileName), rval, nullptr, path, size_path, tolog1, tolog2); 

    return 0; 
}