ฉันจะขยายทูเพิลเป็นอาร์กิวเมนต์ของฟังก์ชันเทมเพลตตัวแปรได้อย่างไร


145

พิจารณากรณีของฟังก์ชันเทมเพลตที่มีอาร์กิวเมนต์แม่แบบตัวแปร:

template<typename Tret, typename... T> Tret func(const T&... t);

ตอนนี้ผมมีขอบเขตของtของค่า ฉันจะเรียกfunc()ใช้ค่าทูเพิลเป็นอาร์กิวเมนต์ได้อย่างไร ฉันได้อ่านเกี่ยวกับbind()วัตถุฟังก์ชันพร้อมcall()ฟังก์ชันและapply()ฟังก์ชันในเอกสารอื่น ๆ ที่ล้าสมัยในปัจจุบัน การใช้งาน GNU GCC 4.4 ดูเหมือนจะมีcall()ฟังก์ชันในbind()คลาสนี้ แต่มีเอกสารประกอบเกี่ยวกับเรื่องนี้น้อยมาก

บางคนแนะนำให้ใช้แฮ็กแบบเรียกซ้ำที่เขียนด้วยมือ แต่มูลค่าที่แท้จริงของอาร์กิวเมนต์แม่แบบตัวแปรคือสามารถใช้ได้ในกรณีเช่นข้างต้น

ใครมีวิธีแก้คือหรือบอกใบ้ว่าจะอ่านได้ที่ไหน


5
มาตรฐาน C ++ 14 มีวิธีแก้ไขให้ดู open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
Skeen

1
แนวคิดคือการแกะทูเพิลด้วยการระเบิดแบบตัวแปรเดียวโดยใช้integer_sequenceดูที่en.cppreference.com/w/cpp/utility/integer_sequence
Skeen

6
การมีinteger_sequence Sคุณเพียงแค่เรียกฟังก์ชันของคุณว่าfunc(std::get<S>(tuple)...)และปล่อยให้คอมไพเลอร์จัดการส่วนที่เหลือ
Skeen

2
หากใช้ C ++ 17 หรือใหม่กว่าให้ละเว้นคำตอบนี้และดูคำตอบด้านล่างโดยใช้ std :: apply
lewis

คำตอบ:


47

นี่คือรหัสของฉันหากใครสนใจ

โดยทั่วไปในเวลาคอมไพล์คอมไพลเลอร์จะยกเลิกการเรียกใช้อาร์กิวเมนต์ทั้งหมดซ้ำ ๆ ในการเรียกฟังก์ชันรวมต่างๆ <N> -> การเรียก <N-1> -> การโทร ... -> การเรียก <0> ซึ่งเป็นครั้งสุดท้ายและคอมไพเลอร์จะปรับให้เหมาะสม ฟังก์ชันระดับกลางต่างๆเรียกใช้เพื่อเก็บเฉพาะฟังก์ชันสุดท้ายซึ่งเทียบเท่ากับ func (arg1, arg2, arg3, ... )

มีให้ 2 เวอร์ชันหนึ่งสำหรับฟังก์ชันที่เรียกใช้กับอ็อบเจ็กต์และอีกเวอร์ชันสำหรับฟังก์ชันคงที่

#include <tr1/tuple>

/**
 * Object Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_obj_func
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_obj_func<N-1>::applyTuple( pObj, f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_obj_func<0>
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    (pObj->*f)( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename T, typename... ArgsF, typename... ArgsT >
void applyTuple( T* pObj,
                 void (T::*f)( ArgsF... ),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_obj_func<sizeof...(ArgsT)>::applyTuple( pObj, f, t );
}

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_func
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_func<N-1>::applyTuple( f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_func<0>
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    f( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename... ArgsF, typename... ArgsT >
void applyTuple( void (*f)(ArgsF...),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_func<sizeof...(ArgsT)>::applyTuple( f, t );
}

// ***************************************
// Usage
// ***************************************

template < typename T, typename... Args >
class Message : public IMessage
{

  typedef void (T::*F)( Args... args );

public:

  Message( const std::string& name,
           T& obj,
           F pFunc,
           Args... args );

private:

  virtual void doDispatch( );

  T*  pObj_;
  F   pFunc_;
  std::tr1::tuple<Args...> args_;
};

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
Message<T, Args...>::Message( const std::string& name,
                              T& obj,
                              F pFunc,
                              Args... args )
: IMessage( name ),
  pObj_( &obj ),
  pFunc_( pFunc ),
  args_( std::forward<Args>(args)... )
{

}

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
void Message<T, Args...>::doDispatch( )
{
  try
  {
    applyTuple( pObj_, pFunc_, args_ );
  }
  catch ( std::exception& e )
  {

  }
}

2
เป็นไปได้หรือไม่ที่จะนำสิ่งนี้มาปรับใช้ในกรณีที่ "ฟังก์ชัน" ที่เป็นปัญหานั้นเป็นตัวสร้างจริง ๆ
HighCommander4

คุณช่วยให้ตัวอย่างสิ่งที่คุณต้องการทำและเราจะไปจากที่นั่น
เดวิด

โซลูชันนี้จัดเตรียมเฉพาะค่าโสหุ้ยของเวลาคอมไพล์และในตอนท้ายมันจะถูกทำให้ง่ายขึ้นเป็น (pObj -> * f) (arg0, arg, 1, ... argN); ขวา?
Goofy

ใช่คอมไพเลอร์จะบีบอัดการเรียกใช้ฟังก์ชันหลายรายการลงในฟังก์ชันสุดท้ายราวกับว่าคุณเขียนขึ้นเองซึ่งเป็นความสวยงามของการเขียนโปรแกรมเมตาทั้งหมดนี้
เดวิด

ทุกtr1สิ่งสามารถนำออกมาได้แล้วด้วย c ++ 11
Ryan Haining

40

ใน C ++ 17 คุณสามารถทำได้:

std::apply(the_function, the_tuple);

ใช้งานได้แล้วใน Clang ++ 3.9 โดยใช้ std :: experiment :: apply

การตอบกลับความคิดเห็นโดยบอกว่าสิ่งนี้จะใช้ไม่ได้หากthe_functionเป็นเทมเพลตสิ่งต่อไปนี้เป็นวิธีแก้ปัญหา:

#include <tuple>

template <typename T, typename U> void my_func(T &&t, U &&u) {}

int main(int argc, char *argv[argc]) {

  std::tuple<int, float> my_tuple;

  std::apply([](auto &&... args) { my_func(args...); }, my_tuple);

  return 0;
}

วิธีแก้ปัญหานี้เป็นวิธีแก้ไขปัญหาทั่วไปในการส่งผ่านชุดโอเวอร์โหลดและเทมเพลตฟังก์ชันที่คาดว่าจะมีฟังก์ชัน การแก้ปัญหาทั่วไป (หนึ่งที่อยู่ในการดูแลของที่สมบูรณ์แบบการส่งต่อ constexpr-Ness และ noexcept-Ness) จะนำเสนอที่นี่: https://blog.tartanllama.xyz/passing-overload-sets/


ตามตัวอย่างรหัสที่std :: ใช้ดูเหมือนว่าจะไม่ทำงานหากthe_functionเป็นเทมเพลต
Zitrax

2
@Zitrax คุณสามารถระบุอาร์กิวเมนต์เทมเพลตของฟังก์ชัน:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
Erbureth กล่าวว่า Reinstate Monica

นี่เป็นวิธีแก้ปัญหาที่เรียบง่ายและสง่างามที่สุด และมันได้ผลอย่างมหัศจรรย์ ขอบคุณมาก M. Alaggan !!!!!! +100 คะแนน
Elliott

36

ใน C ++ มีหลายวิธีในการขยาย / แกะทูเปิลและนำองค์ประกอบทูเพิลเหล่านั้นไปใช้กับฟังก์ชันแม่แบบที่แตกต่างกัน นี่คือคลาสตัวช่วยขนาดเล็กที่สร้างอาร์เรย์ดัชนี มีการใช้มากในการเขียนโปรแกรมแม่แบบ:

// ------------- UTILITY---------------
template<int...> struct index_tuple{}; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
{ 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
}; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
{ 
    typedef index_tuple<Indexes...> type; 
}; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
{}; 

ตอนนี้รหัสที่ใช้งานได้ไม่ใหญ่นัก:

 // ----------UNPACK TUPLE AND APPLY TO FUNCTION ---------
#include <tuple>
#include <iostream> 

using namespace std;

template<class Ret, class... Args, int... Indexes > 
Ret apply_helper( Ret (*pf)(Args...), index_tuple< Indexes... >, tuple<Args...>&& tup) 
{ 
    return pf( forward<Args>( get<Indexes>(tup))... ); 
} 

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), const tuple<Args...>&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), tuple<Args...>(tup));
}

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), tuple<Args...>&&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), forward<tuple<Args...>>(tup));
}

การทดสอบแสดงการร้อง:

// --------------------- TEST ------------------
void one(int i, double d)
{
    std::cout << "function one(" << i << ", " << d << ");\n";
}
int two(int i)
{
    std::cout << "function two(" << i << ");\n";
    return i;
}

int main()
{
    std::tuple<int, double> tup(23, 4.5);
    apply(one, tup);

    int d = apply(two, std::make_tuple(2));    

    return 0;
}

ฉันไม่ได้เชี่ยวชาญภาษาอื่นมากนัก แต่ฉันเดาว่าถ้าภาษาเหล่านี้ไม่มีฟังก์ชันดังกล่าวในเมนูก็ไม่มีทางทำเช่นนั้นได้ อย่างน้อยก็ใช้ C ++ ได้และฉันคิดว่ามันไม่ซับซ้อนมากนัก ...


"... และนำองค์ประกอบทูเพิลเหล่านั้นไปใช้กับฟังก์ชันเทมเพลตที่แตกต่างกัน" ส่วนการทดสอบมีเฉพาะฟังก์ชันตัวแปรที่ไม่ใช่เทมเพลตเท่านั้น ถ้าฉันเพิ่มหนึ่งtemplate<class ... T> void three(T...) {}ไลค์และพยายามใช้ให้ใช้กับที่มันไม่ได้รวบรวม
Zitrax

32

ฉันพบว่านี่เป็นโซลูชันที่หรูหราที่สุด (และได้รับการส่งต่ออย่างเหมาะสมที่สุด):

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a)
        -> decltype(Apply<N-1>::apply(
            ::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        ))
    {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a)
        -> decltype(::std::forward<F>(f)(::std::forward<A>(a)...))
    {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t)
    -> decltype(Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t)))
{
    return Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

ตัวอย่างการใช้งาน:

void foo(int i, bool b);

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&foo, t);
}

น่าเสียดายที่ GCC (อย่างน้อย 4.6) ล้มเหลวในการรวบรวมสิ่งนี้ด้วย "ขออภัยไม่ได้ใช้งาน: mangling overload" (ซึ่งหมายความว่าคอมไพเลอร์ยังไม่ได้ใช้ข้อกำหนด C ++ 11 อย่างสมบูรณ์) และเนื่องจากมันใช้เทมเพลตที่หลากหลายจึงไม่เคยชิน ทำงานใน MSVC ดังนั้นจึงไม่มีประโยชน์มากหรือน้อย อย่างไรก็ตามเมื่อมีคอมไพเลอร์ที่รองรับข้อมูลจำเพาะแล้วมันจะเป็นแนวทางที่ดีที่สุดของ IMHO (หมายเหตุ: มันไม่ยากที่จะแก้ไขสิ่งนี้เพื่อให้คุณสามารถแก้ไขข้อบกพร่องใน GCC หรือนำไปใช้กับ Boost Preprocessor แต่มันทำลายความสง่างามดังนั้นนี่คือเวอร์ชันที่ฉันกำลังโพสต์)

GCC 4.7 รองรับรหัสนี้ได้แล้ว

แก้ไข: เพิ่มไปข้างหน้าสำหรับการเรียกใช้ฟังก์ชันจริงเพื่อรองรับแบบฟอร์มอ้างอิง rvalue * ในกรณีที่คุณใช้ clang (หรือถ้ามีใครเข้ามาเพิ่ม)

แก้ไข: เพิ่มไปข้างหน้าที่ขาดหายไปรอบ ๆ วัตถุฟังก์ชันในเนื้อความของฟังก์ชันใช้งานที่ไม่ใช่สมาชิก ขอบคุณ pheedbaq ที่ชี้ให้เห็นว่ามันหายไป

แก้ไข: และนี่คือเวอร์ชัน C ++ 14 เนื่องจากเป็นรุ่นที่ดีกว่ามาก (ยังไม่ได้รวบรวม):

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a) {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a) {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t) {
    return Apply< ::std::tuple_size< ::std::decay_t<T>
      >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

นี่คือเวอร์ชันสำหรับฟังก์ชั่นสมาชิก (ยังไม่ผ่านการทดสอบมากนัก!):

using std::forward; // You can change this if you like unreadable code or care hugely about namespace pollution.

template<size_t N>
struct ApplyMember
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&& t, A&&... a) ->
        decltype(ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...))
    {
        return ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...);
    }
};

template<>
struct ApplyMember<0>
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&&, A&&... a) ->
        decltype((forward<C>(c)->*forward<F>(f))(forward<A>(a)...))
    {
        return (forward<C>(c)->*forward<F>(f))(forward<A>(a)...);
    }
};

// C is the class, F is the member function, T is the tuple.
template<typename C, typename F, typename T>
inline auto apply(C&& c, F&& f, T&& t) ->
    decltype(ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t)))
{
    return ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t));
}
// Example:

class MyClass
{
public:
    void foo(int i, bool b);
};

MyClass mc;

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&mc, &MyClass::foo, t);
}

1
+1 จากคำตอบที่ระบุไว้คุณเป็นคนที่ใกล้เคียงที่สุดที่ฉันจะได้ทำงานกับอาร์กิวเมนต์ซึ่งอาร์กิวเมนต์เป็นเวกเตอร์ ... ... ideone.com/xH5kBHหากคุณรวบรวมสิ่งนี้ด้วย -DDIRECT_CALL และเรียกใช้คุณจะเห็นว่าผลลัพธ์ควรเป็นอย่างไร ฉันได้รับข้อผิดพลาดในการคอมไพล์เป็นอย่างอื่น (ฉันคิดว่าประเภทปฏิเสธไม่ฉลาดพอที่จะคิดหากรณีพิเศษของฉัน) ด้วย gcc 4.7.2
kfmfe04

3
เวอร์ชันของ gcc บน ideaone นั้นเก่าไปแล้วสำหรับสิ่งนี้จึงไม่รองรับการโอเวอร์โหลดประเภทการส่งคืนแบบแยกส่วน ฉันได้ทดสอบโค้ดนี้อย่างละเอียดแล้วใน gcc 4.7.2 และฉันไม่พบปัญหาใด ๆ ด้วย gcc 4.8 คุณสามารถใช้คุณลักษณะการคืนค่าอัตโนมัติ C ++ 17 ใหม่เพื่อหลีกเลี่ยงประเภทผลตอบแทนต่อท้ายแบบปฏิเสธที่น่ารังเกียจทั้งหมด
DRayX

1
ด้วยความอยากรู้อยากเห็นในapplyฟังก์ชันที่ไม่ใช่สมาชิกเหตุใดจึงfไม่ถูกรวมเข้ากับการstd::forwardโทรเนื่องจากอยู่ในประเภทการส่งคืน มันไม่จำเป็น?
Brett Rossier

3
ด้วยความอยากรู้อยากเห็นฉันลองรวบรวมสิ่งนี้ใน GCC 4.8 และfoo('x', true)รวบรวมเป็นรหัสแอสเซมบลีเดียวกันapply(foo, ::std::make_tuple('x', true))กับระดับการเพิ่มประสิทธิภาพใด ๆ นอกเหนือจาก -O0
DRayX

2
ด้วย C ++ 14 integer_sequenceคุณจะได้รับการใช้งานที่เกือบถูกต้องapply()ในตัวอย่าง ดูคำตอบของฉันด้านล่าง
PeterSom

30
template<typename F, typename Tuple, std::size_t ... I>
auto apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename Tuple>
auto apply(F&& f, Tuple&& t) {
    using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}

สิ่งนี้ดัดแปลงมาจากแบบร่าง C ++ 14 โดยใช้ index_sequence ฉันอาจเสนอให้ใช้กับมาตรฐานในอนาคต (TS)


1

ข่าวดูไม่ดี

หลังจากอ่านมาตรฐานฉบับร่างที่เพิ่งเปิดตัวฉันไม่เห็นวิธีแก้ปัญหาในตัวสำหรับสิ่งนี้ซึ่งดูเหมือนจะแปลก

สถานที่ที่ดีที่สุดในการถามเกี่ยวกับเรื่องดังกล่าว (ถ้าคุณยังไม่ได้ทำ) คือ comp.lang.c ++. กลั่นกรองเนื่องจากมีคนบางคนที่เกี่ยวข้องกับการร่างโพสต์มาตรฐานที่นั่นเป็นประจำ

หากคุณดูหัวข้อนี้มีคนมีคำถามเหมือนกัน (อาจเป็นคุณซึ่งในกรณีนี้คุณจะพบว่าคำตอบทั้งหมดนี้น่าผิดหวังเล็กน้อย!) และแนะนำให้ใช้งานที่น่าเกลียดเล็กน้อย

ฉันแค่สงสัยว่ามันจะง่ายกว่าไหมที่จะทำให้ฟังก์ชันยอมรับไฟล์ tupleเนื่องจากการแปลงวิธีนั้นง่ายกว่า แต่นี่หมายความว่าฟังก์ชันทั้งหมดควรยอมรับทูเพิลเป็นอาร์กิวเมนต์เพื่อความยืดหยุ่นสูงสุดและนั่นแสดงให้เห็นถึงความแปลกประหลาดของการไม่จัดเตรียมส่วนขยายในตัวของทูเพิลไปยังฟังก์ชันอาร์กิวเมนต์แพ็ค

อัปเดต: ลิงก์ด้านบนใช้ไม่ได้ - ลองวางสิ่งนี้:

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/750fa3815cdaac45/d8dc09e34bbb9661?lnk=gst&q=tuple+variadic#d8dc09e34bbb9661


ฉันสงสัยว่าทำไมพวกเขาถึงต้องกังวลกับการแยกชุดอาร์กิวเมนต์ทูเพิลและฟังก์ชัน บางทีในคอมไพเลอร์ที่เข้ากันได้พวกเขาสามารถใช้แทนกันได้ แต่ฉันไม่ได้เห็นสิ่งบ่งชี้ว่าทุกที่ที่ฉันอ่านเกี่ยวกับพวกเขา
Daniel Earwicker

2
เนื่องจาก tuple <int, char, string> เป็นประเภทแยกต่างหาก เช่นเดียวกับความสามารถในการสร้างฟังก์ชันที่ไม่ต้องใช้ make_type ในระหว่างการโทรทุกครั้ง
coppro

1
นอกจากนี้สถานที่ที่ดีที่สุดไม่ใช่ comp.lang.c ++. คำถามเกี่ยวกับ C ++ 1x มักจะนำไปที่ comp.std.c ++ ได้ดีกว่าเสมอ
coppro

1

การใช้งานทั้งหมดนี้เป็นสิ่งที่ดี แต่เนื่องจากการใช้ตัวชี้ไปยังคอมไพเลอร์ฟังก์ชันสมาชิกมักไม่สามารถอินไลน์การเรียกฟังก์ชันเป้าหมายได้ (อย่างน้อย gcc 4.8 ทำไม่ได้ไม่ว่าเหตุใด gcc จึงไม่สามารถกำหนดตัวชี้ฟังก์ชันแบบอินไลน์ได้ )

แต่สิ่งต่างๆจะเปลี่ยนไปหากส่งตัวชี้ไปยังฟังก์ชันสมาชิกเป็นอาร์กิวเมนต์ของเทมเพลตไม่ใช่พารามิเตอร์ของฟังก์ชัน:

/// from https://stackoverflow.com/a/9288547/1559666
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

template<typename TT>
using makeSeq = typename gens< std::tuple_size< typename std::decay<TT>::type >::value >::type;


// deduce function return type
template<class ...Args>
struct fn_type;

template<class ...Args>
struct fn_type< std::tuple<Args...> >{

    // will not be called
    template<class Self, class Fn>
    static auto type_helper(Self &self, Fn f) -> decltype((self.*f)(declval<Args>()...)){
        //return (self.*f)(Args()...);
        return NULL;
    }
};

template<class Self, class ...Args>
struct APPLY_TUPLE{};

template<class Self, class ...Args>
struct APPLY_TUPLE<Self, std::tuple<Args...>>{
    Self &self;
    APPLY_TUPLE(Self &self): self(self){}

    template<class T, T (Self::* f)(Args...),  class Tuple>
    void delayed_call(Tuple &&list){
        caller<T, f, Tuple >(forward<Tuple>(list), makeSeq<Tuple>() );
    }

    template<class T, T (Self::* f)(Args...), class Tuple, int ...S>
    void caller(Tuple &&list, const seq<S...>){
        (self.*f)( std::get<S>(forward<Tuple>(list))... );
    }
};

#define type_of(val) typename decay<decltype(val)>::type

#define apply_tuple(obj, fname, tuple) \
    APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
            decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
            &decay<decltype(obj)>::type::fname \
            > \
            (tuple);

และการใช้งาน:

struct DelayedCall
{  
    void call_me(int a, int b, int c){
        std::cout << a+b+c;
    }

    void fire(){
        tuple<int,int,int> list = make_tuple(1,2,3);
        apply_tuple(*this, call_me, list); // even simpler than previous implementations
    }
};

หลักฐานการไม่สามารถตรวจสอบได้http://goo.gl/5UqVnC


ด้วยการเปลี่ยนแปลงเล็กน้อยเราสามารถ "โอเวอร์โหลด" apply_tuple:

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(X,##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define VARARG_IMPL_(base, count, ...) base##count(__VA_ARGS__)
#define VARARG_IMPL(base, count, ...) VARARG_IMPL_(base, count, __VA_ARGS__)
#define VARARG(base, ...) VARARG_IMPL(base, VA_NARGS(__VA_ARGS__), __VA_ARGS__)

#define apply_tuple2(fname, tuple) apply_tuple3(*this, fname, tuple)
#define apply_tuple3(obj, fname, tuple) \
    APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
            decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
            &decay<decltype(obj)>::type::fname \
            /* ,decltype(tuple) */> \
            (tuple);
#define apply_tuple(...) VARARG(apply_tuple, __VA_ARGS__)

...

apply_tuple(obj, call_me, list);
apply_tuple(call_me, list);       // call this->call_me(list....)

นอกจากนี้ยังเป็นโซลูชันเดียวที่ใช้งานได้กับฟังก์ชันเทมเพลต


1

1) หากคุณมีโครงสร้าง parameter_pack สำเร็จรูปเป็นอาร์กิวเมนต์ของฟังก์ชันคุณสามารถใช้ std :: tie ดังนี้:

template <class... Args>
void tie_func(std::tuple<Args...> t, Args&... args)
{
 std::tie<Args...>(args...) = t;
}

int main()
{
 std::tuple<int, double, std::string> t(2, 3.3, "abc");

 int i;
 double d;
 std::string s;

 tie_func(t, i, d, s);

 std::cout << i << " " << d << " " << s << std::endl;
}

2) หากคุณไม่มีอาร์กิวเมนต์พาราแพ็คสำเร็จรูปคุณจะต้องคลาย tuple แบบนี้

#include <tuple>
#include <functional>
#include <iostream>



template<int N>
struct apply_wrap {
    template<typename R, typename... TupleArgs, typename... UnpackedArgs>
    static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>& t, UnpackedArgs... args )
    {
        return apply_wrap<N-1>::applyTuple( f, t, std::get<N-1>( t ), args... );
    }
};


template<>
struct apply_wrap<0>
{
    template<typename R, typename... TupleArgs, typename... UnpackedArgs>
    static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>&, UnpackedArgs... args )
    {
        return f( args... );
    }
};



template<typename R, typename... TupleArgs>
R applyTuple( std::function<R(TupleArgs...)>& f, std::tuple<TupleArgs...> const& t )
{
    return apply_wrap<sizeof...(TupleArgs)>::applyTuple( f, t );
}



int fac(int n)
{
    int r=1;
    for(int i=2; i<=n; ++i)
        r *= i;
    return r;
}



int main()
{
    auto t = std::make_tuple(5);
    auto f = std::function<decltype(fac)>(&fac);
    cout << applyTuple(f, t);
}

0

แล้วสิ่งนี้ล่ะ:

// Warning: NOT tested!
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

using std::declval;
using std::forward;
using std::get;
using std::integral_constant;
using std::size_t;
using std::tuple;

namespace detail
{
    template < typename Func, typename ...T, typename ...Args >
    auto  explode_tuple( integral_constant<size_t, 0u>, tuple<T...> const &t,
     Func &&f, Args &&...a )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    { return forward<Func>( f )( forward<Args>(a)... ); }

    template < size_t Index, typename Func, typename ...T, typename ...Args >
    auto  explode_tuple( integral_constant<size_t, Index>, tuple<T...> const&t,
     Func &&f, Args &&...a )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    {
        return explode_tuple( integral_constant<size_t, Index - 1u>{}, t,
         forward<Func>(f), get<Index - 1u>(t), forward<Args>(a)... );
    }
}

template < typename Func, typename ...T >
auto  run_tuple( Func &&f, tuple<T...> const &t )
 -> decltype( forward<Func>(f)(declval<T const>()...) )
{
    return detail::explode_tuple( integral_constant<size_t, sizeof...(T)>{}, t,
     forward<Func>(f) );
}

template < typename Tret, typename ...T >
Tret  func_T( tuple<T...> const &t )
{ return run_tuple( &func<Tret, T...>, t ); }

run_tupleแม่แบบฟังก์ชั่นใช้เวลา tuple ที่กำหนดและผ่านองค์ประกอบของบุคคลเพื่อฟังก์ชั่นที่กำหนด ดำเนินการโดยการเรียกใช้เทมเพลตฟังก์ชันตัวช่วยexplode_tupleซ้ำ สิ่งสำคัญคือต้องrun_tupleส่งขนาดของทูเปิลไปที่explode_tuple; ตัวเลขนั้นทำหน้าที่เป็นตัวนับจำนวนองค์ประกอบที่จะแยกออกมา

หากทูเปิลว่างเปล่าให้run_tupleเรียกเวอร์ชันแรกexplode_tupleด้วยฟังก์ชันรีโมตเป็นอาร์กิวเมนต์อื่นเท่านั้น ฟังก์ชันรีโมตถูกเรียกโดยไม่มีอาร์กิวเมนต์และเราทำเสร็จแล้ว หากทูเปิลไม่ว่างเปล่าตัวเลขที่สูงกว่าจะถูกส่งไปยังเวอร์ชันที่สองของถูกสร้างขึ้นโดยมีอาร์กิวเมนต์เดียวกันยกเว้นจำนวนตัวนับจะลดลงหนึ่งรายการและ (การอ้างอิงถึง) องค์ประกอบทูเปิลสุดท้ายจะถูกตรึงไว้เป็นอาร์กิวเมนต์หลัง ฟังก์ชั่นระยะไกล ในการเรียกแบบเรียกซ้ำตัวนับไม่ได้เป็นศูนย์และการโทรอีกครั้งจะถูกสร้างขึ้นโดยตัวนับลดลงอีกครั้งและองค์ประกอบที่ไม่ได้อ้างอิงถัดไปจะถูกแทรกในรายการอาร์กิวเมนต์หลังจากฟังก์ชันระยะไกล แต่ก่อนที่อาร์กิวเมนต์แทรกอื่น ๆ หรือตัวนับถึง ศูนย์และฟังก์ชันรีโมตถูกเรียกใช้ด้วยทั้งหมดexplode_tupleพร้อมกับฟังก์ชันรีโมต การเรียกซ้ำไปยังexplode_tuple ข้อโต้แย้งที่สะสมหลังจากนั้น

ฉันไม่แน่ใจว่าฉันมีไวยากรณ์ในการบังคับให้เทมเพลตฟังก์ชันเวอร์ชันใดเวอร์ชันหนึ่งถูกต้อง ฉันคิดว่าคุณสามารถใช้ตัวชี้ต่อฟังก์ชันเป็นวัตถุฟังก์ชันได้ คอมไพเลอร์จะแก้ไขโดยอัตโนมัติ


0

ฉันกำลังประเมิน MSVS 2013RC และไม่สามารถรวบรวมโซลูชันก่อนหน้าบางส่วนที่เสนอไว้ที่นี่ในบางกรณี ตัวอย่างเช่น MSVS จะไม่สามารถรวบรวมผลตอบแทน "อัตโนมัติ" หากมีพารามิเตอร์ฟังก์ชันมากเกินไปเนื่องจากขีด จำกัด การ จำกัด เนมสเปซ (ฉันส่งข้อมูลนั้นไปยัง Microsoft เพื่อแก้ไข) ในกรณีอื่น ๆ เราจำเป็นต้องเข้าถึงการกลับมาของฟังก์ชันแม้ว่าจะสามารถทำได้ด้วย lamda: สองตัวอย่างต่อไปนี้ให้ผลลัพธ์เหมือนกัน ..

apply_tuple([&ret1](double a){ret1 = cos(a); }, std::make_tuple<double>(.2));
ret2 = apply_tuple((double(*)(double))cos, std::make_tuple<double>(.2));

และขอขอบคุณอีกครั้งสำหรับผู้ที่โพสต์คำตอบที่นี่ต่อหน้าฉันฉันจะไม่ได้รับสิ่งนี้หากไม่มีมัน ... ดังนั้นนี่คือ:

template<size_t N>
struct apply_impl {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&& t, A&&... a)
    -> decltype(apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
    -> decltype(apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
    }
};

// This is a work-around for MSVS 2013RC that is required in some cases
#if _MSC_VER <= 1800 /* update this when bug is corrected */
template<>
struct apply_impl<6> {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&& t, A&&... a)
    -> decltype(std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
    -> decltype((o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return (o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
    }
};
#endif

template<>
struct apply_impl<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&&, A&&... a)
    -> decltype(std::forward<F>(f)(std::forward<A>(a)...)) {
         return std::forward<F>(f)(std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&&, A&&... a)
    -> decltype((o->*std::forward<F>(f))(std::forward<A>(a)...)) {
         return (o->*std::forward<F>(f))(std::forward<A>(a)...);
    }
};

// Apply tuple parameters on a non-member or static-member function by perfect forwarding
template<typename F, typename T>
inline auto apply_tuple(F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t))) {
     return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t));
}

// Apply tuple parameters on a member function
template<typename C, typename F, typename T>
inline auto apply_tuple(C*const o, F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t))) {
     return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t));
}

ทำไมคุณทำให้อาร์กิวเมนต์วัตถุเป็นตัวชี้ const? ไม่อ้างอิงไม่อ้างอิง const ไม่ใช่แค่ตัวชี้? จะเกิดอะไรขึ้นถ้าฟังก์ชันที่เรียกได้จะไม่ทำconst?
tower120

0

การขยายโซลูชันของ @ David คุณสามารถเขียนเทมเพลตแบบวนซ้ำได้

  1. ไม่ได้ใช้ (สุดเหวี่ยง verbose, IMO) integer_sequenceความหมาย
  2. ไม่ใช้พารามิเตอร์เทมเพลตชั่วคราวเพิ่มเติมint Nเพื่อนับการวนซ้ำแบบวนซ้ำ
  3. (ทางเลือกสำหรับ functors แบบคงที่ / โกลบอล) ใช้ functor เป็นพารามิเตอร์เทมเพลตสำหรับการเพิ่มประสิทธิภาพเวลาคอมไพล์

เช่น:

template <class F, F func>
struct static_functor {
    template <class... T, class... Args_tmp>
    static inline auto apply(const std::tuple<T...>& t, Args_tmp... args)
            -> decltype(func(std::declval<T>()...)) {
        return static_functor<F,func>::apply(t, args...,
                std::get<sizeof...(Args_tmp)>(t));
    }
    template <class... T>
    static inline auto apply(const std::tuple<T...>& t, T... args)
            -> decltype(func(args...)) {
        return func(args...);
    }
};

static_functor<decltype(&myFunc), &myFunc>::apply(my_tuple);

หรืออีกวิธีหนึ่งถ้า functor ของคุณไม่ได้กำหนดไว้ในเวลาคอมไพล์ (เช่นconstexprอินสแตนซ์ที่ไม่ใช่functor หรือนิพจน์แลมบ์ดา) คุณสามารถใช้เป็นพารามิเตอร์ของฟังก์ชันแทนพารามิเตอร์เทมเพลตคลาสและในความเป็นจริงลบคลาสที่มีทั้งหมด:

template <class F, class... T, class... Args_tmp>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        Args_tmp... args) -> decltype(func(std::declval<T>()...)) {
    return apply_functor(func, t, args..., std::get<sizeof...(Args_tmp)>(t));
}
template <class F, class... T>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        T... args) -> decltype(func(args...)) {
    return func(args...);
}

apply_functor(&myFunc, my_tuple);

สำหรับการเรียกใช้ฟังก์ชันแบบพอยน์เตอร์ถึงสมาชิกคุณสามารถปรับโค้ดด้านบนอย่างใดอย่างหนึ่งได้เช่นเดียวกับในคำตอบของ @ David

คำอธิบาย

ในการอ้างอิงถึงชิ้นที่สองของรหัสที่มีสองฟังก์ชั่นแม่แบบ: ครั้งแรกที่หนึ่งจะใช้เวลา functor func, tuple tกับชนิดT...และชุดพารามิเตอร์ประเภทargs Args_tmp...เมื่อถูกเรียกมันจะเพิ่มอ็อบเจ็กต์จากtไปยังแพ็กพารามิเตอร์ทีละรายการซ้ำ ๆ ตั้งแต่จุดเริ่มต้น ( 0) ถึงจุดสิ้นสุดและเรียกใช้ฟังก์ชันอีกครั้งด้วยแพ็กพารามิเตอร์ที่เพิ่มขึ้นใหม่

ลายเซ็นของฟังก์ชั่นที่สองเกือบจะเหมือนกับครั้งแรกยกเว้นว่าจะใช้ชนิดสำหรับแพ็คพารามิเตอร์T... argsดังนั้นเมื่อargsในการทำงานครั้งแรกที่สมบูรณ์เต็มไปด้วยค่าจากtก็ชนิดจะเป็นT...(ในหลอกโค้ดtypeid(T...) == typeid(Args_tmp...)) func(args...)และทำให้คอมไพเลอร์แทนจะเรียกฟังก์ชั่นมากเกินไปที่สองซึ่งในการเปิดสาย

รหัสในตัวอย่าง functor แบบคงที่ทำงานเหมือนกันโดยใช้ functor แทนเป็นอาร์กิวเมนต์เทมเพลตคลาส


ความคิดเห็นใด ๆ เกี่ยวกับการเพิ่มประสิทธิภาพเวลาคอมไพล์ของตัวเลือกแรกจะได้รับการชื่นชมดังนั้นฉันจึงสามารถทำให้คำตอบของฉันสมบูรณ์ยิ่งขึ้น (และอาจจะได้เรียนรู้สิ่งใหม่ ๆ )
CrepeGoat

-3

ทำไมไม่เพียงรวมอาร์กิวเมนต์ตัวแปรของคุณลงในคลาสทูเพิลแล้วใช้การเรียกซ้ำเวลาคอมไพล์ (ดูลิงค์ ) เพื่อดึงดัชนีที่คุณสนใจฉันพบว่าการคลายเทมเพลตตัวแปรลงในคอนเทนเนอร์หรือคอลเล็กชันอาจไม่ใช่ประเภทที่ไม่ปลอดภัย

template<typename... Args>
auto get_args_as_tuple(Args... args) -> std::tuple<Args...> 
{
    return std::make_tuple(args);
}

6
คำถามคืออีกทางหนึ่ง ไม่ได้Args...-> tupleแต่->tuple Args...
Xeo

-4

วิธีง่ายๆนี้ใช้ได้กับฉัน:

template<typename... T>
void unwrap_tuple(std::tuple<T...>* tp)
{
    std::cout << "And here I have the tuple types, all " << sizeof...(T) << " of them" << std::endl;
}

int main()
{
    using TupleType = std::tuple<int, float, std::string, void*>;

    unwrap_tuple((TupleType*)nullptr); // trick compiler into using template param deduction
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.