3
votes

My function calls a Python function with a set of given input arguments (variadic) and returns a tuple containing the function's output (variadic because output varies on the function called).

I'm compiling on a Windows 10 machine via MinGW-w64 compiler's port of g++ using C++11. I've declared this templated variadic function (and call it) as follows:

// Declaration (in PyInterface.h)
template <typename... Inputs, typename... Outputs>
const std::tuple<Outputs...>& callFunction(const std::string &modulePath, const std::string &funcName, Inputs&&... args) { 
    // Calls a Python function & packs output variables into a std::tuple... 
}

// Sample call (in main.cpp)
const std::tuple<std::string, std::string> output = pi.callFunction(myModulePath, myFuncName, inputArg1, inputArg2, inputArg3);

However, this error is thrown (shortened with ... for readability):

conversion from 'const std::tuple<>' to non-scalar type 'const std::tuple<std::__cxx11::basic_string<...>, std::__cxx11::basic_string<...> >' requested

As far as I'm aware using two variadic templates is legal. Additionally, it seems to me that the return type is explicitly set by the variable (const std::tuple<std::string, std::string> output) I am attempting to assign the result of the function to, so the compiler should know what the desired return type is.

Originally, I thought that the error simply indicated that I wasn't using the variadic templates appropriately. I tried using nested template parameters (illustrated here) to specify the return type (i.e., const T<Outputs...>& callFunction). However, this only succeeded in producing an error message indicating that template deduction of T had failed.

This error indicates that my intuition is wrong and that, in my original function, the compiler isn't deducing the desired return type from output's type.

Why is my intuition wrong? How do I correctly use a variadic template to specify this function's return type?

2
You never specify the types for Outputs so there are none. - NathanOliver
@NathanOliver Could you elaborate as to how I specify the types for Outputs? I already tried to modify my call to pi.callFunction<std::string>, but I couldn't figure out what the error message meant: error: cannot bind 'const string {aka const std::__cxx11::basic_string<char>}' lvalue to 'std::__cxx11::basic_string<char>&&' - Vladislav Martin
You are explicitly specifying string as an Input type. You get an rvalue reference with that is combined with the function argument declaration Inputs &&. You are passing an lvalue of const string which would at least require removal of the const before it could be used. Besides the answer I gave where the Input types are picked up implicitly, you could explicitly specify const string & as an Input type, which remains a const string & when combined with Inputs &&. - Khouri Giordano

2 Answers

1
votes

If you want to deduce the return type based on the type of an object being returned that is in the scope of the function, if you are on C++14 then you can just use auto as the return type. It works like a charm

template <typename... Inputs>
const auto& callFunction(
    const std::string &modulePath, 
    const std::string &funcName, 
    Inputs&&... args) { 

    return object;
}

As always beware that you are returning a const reference from this function.

If you are using C++11 then you can use trailing return types, for example

template <typename T>
auto identity_return(T& obj) -> decltype(obj) {
    return obj;
}

where decltype(obj) is the type of the thing you want to return. Again beware in this case it is again a reference.

In general though try and return values from a function, returning references can lead to dangling references if you are not sure what you are returning will outlive the return.


Another solution is to just specify the Outputs... type list manually and let the compiler deduce the types Inputs...

template <typename... Outputs, typename... Inputs>
const std::tuple<Outputs...>& callFunction(
    const std::string &modulePath, 
    const std::string &funcName, 
    Inputs&&... args) { ... }

auto tup = callFunction<std::string, std::string>(modulePath, funcName, args);
0
votes

As usual with C++ compiler error messages, the first one is the only one you need to look at. I'm guessing (because you didn't provide the code) that you are creating a tuple value and then returning a const reference to it. At the end of the function, the tuple is destroyed so what should the reference refer to? You should just return tuple<Outputs...>.

I think you can achieve what you want by swapping the Input and Output type packs. This code also illustrates iterating over the Inputs type pack with the show function. You can do a similar thing to build a tuple<Outputs...> value from a Python tuple object and the Outputs type pack.

#include <iostream>
#include <tuple>
#include <utility>

using namespace std;

template< typename Output > struct convert_t;
template<> struct convert_t< string >
{
    string operator()( const char *text ) const
    {
        return text;
    }

    string operator()( double val ) const
    {
        return to_string( val );
    }
};

inline void show()
{}

template< typename First, typename... Others >
inline void show( First &&first, Others&&... others )
{
    cout << first << "\n";
    show( forward< Others >( others )... );
}

template< typename... Outputs, typename... Inputs >
inline tuple< Outputs... > funkyfunc( Inputs&&... inputs )
{
    cout << "Inputs...\n";
    show( forward< Inputs >( inputs )... );
    return tuple< Outputs... >( convert_t< Outputs >()( inputs )... );
}

int main()
{
    auto t = funkyfunc< string, string >( "text", 123.0 );
    cout << "Outputs...\n";
    cout << get<0>( t ) << "\n";
    cout << get<1>( t ) << "\n";

    return 0;
}