2017-11-16 28 views
7

मैं एक लाइब्रेरी विकसित कर रहा हूं जो गैर टाइप किए गए सी फ़ंक्शंस (SQLite) से संबंधित है और मैं इसे मजबूत टाइप करना चाहता हूं।कटौती का उपयोग कर सी ++ वैरिएडिक विस्तार

विचार FieldDef मजबूत प्रकार है जो उपयोगकर्ता को कमजोर डीबी प्रकारों के लिए int, double और std :: string जैसे कच्चे प्रकारों को जोड़ने की अनुमति देता है। मेरी समस्या यह है कि लाइब्रेरी का अर्थात् बहुत भारी है, और मैं कुछ स्वचालित प्रकार की कटौती जोड़ना चाहता हूं।

namespace FieldType { 
    struct Integer { using rawtype = int; }; 
    struct Real{ using rawtype = double; }; 
    struct Text{ using rawtype = std::string; }; 
    struct Blob{ using rawtype = std::vector<uint8_t>; }; 
} 

मैं भी एक insert एक और query कार्यों कि डालने और SQL कथन का उपयोग किए बिना तालिकाओं से क्वेरी की अनुमति है:

तो मैं "बुनियादी प्रकार" का एक समूह है। क्वेरी सादा चयन होगा। वैसे भी। इच्छित उपयोग है:

FieldDef<FieldType::Integer> mId = makeFieldDef("id", FieldType::Integer()).primaryKey().autoincrement(); 
FieldDef<FieldType::Text> mName = makeFieldDef("name", FieldType::Text()); 
FieldDef<FieldType::Integer> mValue = makeFieldDef("value", FieldType::Integer()); 

SQLiteTable::insert(std::make_tuple(mName, mValue), std::make_tuple(record.name, record.value)); 

std::vector<Record> r; 
SQLiteTable::query 
      (std::make_tuple(mName, mValue), [&r](std::tuple<std::string, int> res) { 
     r.push_back(Record{std::get<0>(res), std::get<1>(res)}); 
}); 

मैं डालने इस तरह से लागू किया: जब यह बुला संकलक असमर्थ है

template <typename ...Ts, typename ...Us> 
void query(std::tuple<Ts...> def, std::function<void(std::tuple<Us...>)> resultFeedbackFunc) { 
    ... 
} 

:

template <typename ...Ts, typename ...Us> 
bool insert (std::tuple<Ts...> def, std::tuple<Us...> values) { 
    std::ostringstream ss; 
    ss << "INSERT INTO " << mName << "(" 
            << buildSqlInsertFieldList<0>(def) 
            << ") VALUES (" 
            << buildSqlInsertValuesListPlaceholder<0>(values) 
            << ");"; 
    auto stmt = newStatement(ss.str()); 
    bindAllValues<0>(stmt.get(), values); 
    return execute(stmt.get()); 
} 

यह ठीक काम करता है, समस्याओं क्वेरी के साथ आते हैं प्रकारों को सही ढंग से कम करने के लिए, इसलिए मुझे लगता है कि इसे एक पैडेंटिक निर्माण की आवश्यकता है:

SQLiteTable::query<FieldType::Text, FieldType::Integer, /* whatever */> (...) 

यह अनैतिक और वर्बोज़ है।

  1. यह क्वेरी समारोह को आसान बनाने के लिए संभव होगा? चूंकि हमारे पास उपयोग में बाधा है, इसलिए Us पैक FieldType::*:rawtype के साथ कुछ प्रकार के अनुकूल हो सकता है, मैं पूछ रहा हूं कि कुछ ऐसे निर्माण का उपयोग करना संभव होगा जो अनपॅक और विधि लागू करें।

    template<typename Ts...> 
    bool insert (std::tuple<Ts...> def, std::tuple<Ts::rawtype ...> values) 
    
  2. के बजाय का उपयोग कर tuples, क्या variadic पैक का उपयोग कर के बारे में: insert के मामले में, यह की तरह कुछ के साथ सरल किया जा सकता है? मैं यह परीक्षण नहीं किया है, लेकिन मुझे डर है की तरह

    template<typename Ts..., typename Us....> 
    bool insert (Ts... def, Us ... values) 
    

कुछ का उपयोग कर संकलक भ्रमित हैं और चीजों को बदतर बना होगा। आपको क्या लगता है?

  1. यदि क्वेरी के वास्तविक कार्यान्वयन का उपयोग करना संभव है, तो उपयोग कोड को अधिक अभिव्यक्तिपूर्ण बनाने के लिए एक कार्यवाही क्या होगी?

यहाँ कोड के बारे में कुछ विवरण हैं, समझाने के लिए:

क्वेरी समारोह निम्नलिखित स्यूडोकोड का उपयोग कर कार्यान्वित किया जाता है:

template <typename ...Ts, typename ...Us> 
void query(std::tuple<Ts...> def, std::function<void(std::tuple<Us...>)> resultFeedbackFunc) { 
    std::ostringstream ss; 
    ss << "SELECT " << buildSqlInsertFieldList<0>(def) << " FROM " << mName <<";"; 
    auto stmt = newStatement(ss.str()); 

    auto r = execute(stmt.get()); 
    SQLiteException::throwIfNotOk(r, db()->handle()); 

    while (hasData(stmt.get())) { 
     auto nColumns = columnCount(stmt.get()); 
     if (nColumns != sizeof...(Ts)) 
      throw std::runtime_error("Column count differs from data size"); 

     std::tuple<Us...> res; 
     getAllValues<0>(stmt.get(), res); 
     resultFeedbackFunc(res); 
    } 
}; 

Statement एक अपारदर्शी प्रकार है कि sqlite छुपाता है कथन संरचना, query विधि newStatement, execute और columnsCount में उपयोग किए जाने वाले अन्य कार्यों के रूप में। फंक्शन getAllValuestuple भरने के लिए रिकर्सन का उपयोग करता है। इसलिए डेटाबेस की प्रत्येक पंक्ति के लिए मजेदार resultFeedbackFunc() कहा जाएगा। तो क्लाइंट कोड, उदाहरण के लिए, एक कंटेनर (एक वेक्टर की तरह) भर सकते हैं।


अद्यतन:

मैं @ bolov के समाधान पीछा किया, और @ मासीमिलियानो-जोन्स के सुधार जोड़ा।

यह प्रतिक्रिया कार्य करने के लिए आंतरिक कॉल का सही कार्यान्वयन है:

resultFeedbackFunc(getValueR<decltype (std::get<Is>(def).rawType())> 
     (stmt.get(), Is)...); 

getValueRsqlite_column_xxx(sqlite3_stmt *, int index) करने के लिए आंतरिक कॉल करता है। अगर मैं सही ढंग से समझता हूं, तो अनपॅकिंग काम करता है क्योंकि तर्क सूची अनपॅकिंग के लिए एक वैध संदर्भ है। अगर मैं तर्कों के बाहर कॉल करना चाहता था, तो मुझे एक फोल्डिंग (या कामकाज करना था क्योंकि मैं सी ++ 11 का उपयोग कर रहा हूं)।

+0

हो, परियोजना यहाँ प्रकाशित किया जाता है: https://github.com/studiofuga/mSqliteCpp – HappyCactus

उत्तर

6

विशिष्ट सहायता देना मुश्किल है क्योंकि आपकी पोस्ट से महत्वपूर्ण कोड भाग गायब हैं।

हालांकि यहां मेरे 2 सेंट हैं। कृपया ध्यान दें कि मैंने अपनी कल्पना के साथ आपके कोड के लापता हिस्सों को भर दिया है।

सबसे पहले आपको std::function तर्क से छुटकारा पाने की आवश्यकता है। std::function का उपयोग केवल तभी करें जब आपको उस प्रकार के मिटाए जाने की आवश्यकता हो। आपके मामले में (कम से कम आपके द्वारा दिखाए गए कोड से) आपको इसकी आवश्यकता नहीं है। तो हम इसे एक साधारण template <class F> पैरामीटर के साथ प्रतिस्थापित करते हैं। यह कटौती की समस्या हल करता है।

अब जब आप एक अवैध मज़ेदार पास करते हैं तो आपको अपने query कार्यान्वयन के कटोरे में गहन संकलन त्रुटि मिल जाएगी। यदि आप इसे नहीं चाहते हैं और आप तेजी से विफल होना चाहते हैं, तो कुछ विकल्प हैं। मैंने आपको decltype के साथ एक SFINAE दृष्टिकोण दिखाने का विकल्प चुना है।

namespace FieldType 
{ 
    struct Integer { using rawtype = int; }; 
    struct Real{ using rawtype = double; }; 
    struct Text{ using rawtype = std::string; }; 
}; 

template <class FT> 
struct FieldDef 
{ 
    using Type = FT; 
    using RawTye = typename Type::rawtype; 

    auto getRaw() -> RawTye { return {}; } 
}; 

template <class... Args, class F, std::size_t... Is> 
auto query_impl(std::tuple<Args...> def, F f, std::index_sequence<Is...>) 
    -> decltype(f(std::get<Is>(def).getRaw()...), std::declval<void>()) 
{ 
    f(std::get<Is>(def).getRaw()...); 
} 

template <class... Args, class F> 
auto query(std::tuple<Args...> def, F f) 
    -> decltype(query_impl(def, f, std::make_index_sequence<sizeof...(Args)>{})) 
{ 
    query_impl(def, f, std::make_index_sequence<sizeof...(Args)>{}); 
} 
auto test() 
{ 
    FieldDef<FieldType::Text> mName = {}; 
    FieldDef<FieldType::Integer> mValue = {}; 

    query(std::make_tuple(mName, mValue), [](std::string, int) {});  // OK 

    query(std::make_tuple(mName, mValue), [](std::string, int, int) {}); // Error 
    query(std::make_tuple(mName, mValue), [](int, int) {});    // Error 
} 

अवैध कॉल संदेश सदृश करने के साथ विफल: अपनी बात 2. के बारे में

error: no matching function for call to 'query' 
... 
note: candidate template ignored: substitution failure [with Args = ...]: 
    no matching function for call to 'query_impl' 
... 

कि छूट नहीं होगा। और यदि यह भी होगा, तो आप पठनीयता के लिए पैरामीटर को समूहबद्ध करना चाहते हैं। अर्थात। आप insert(a, b, c, d) के बजाय insert({a, b}, {c, d}) चाहते हैं।

मुझे आपकी बात 3 समझ में नहीं आ रही है।

+0

बहुत बहुत शुक्रिया! यह एक तरह का इंटरफ़ेस है जिसे मैं सोच रहा था। मैंने सवाल में कुछ और विवरण दिया है, लेकिन आपके द्वारा किए गए अधिकांश मान्यताओं सही हैं। मैं आपकी संरचना को वास्तविक कोड में अनुकूलित करने की कोशिश करने पर संघर्ष कर रहा हूं लेकिन मुझे लगता है कि मेरे पास आवश्यक सारी जानकारी है, धन्यवाद। लगभग 2), ठीक है जैसा मैंने अनुमान लगाया था। लगभग 3 भूल जाओ), चूंकि हमारे पास एक सुरुचिपूर्ण समाधान है, इसलिए हमें किसी भी कामकाज की आवश्यकता नहीं है। – HappyCactus

2
  1. टुपल्स का उपयोग करने के बजाय, वैराडिक पैक का उपयोग करने के बारे में क्या?

आप

template<typename T,typename V> 
struct FieldInsert{ V const& ref; }; 

template<typename T> 
struct FieldDef 
{ 
    template<typename V> 
    auto operator()(V const& value) const{ return FieldInsert<T,V>{value}; } 
}; 

template<typename... T> 
bool insert(T... args) 
{ 
    // defines buildSqlInsertFieldList and buildSqlInsertValuesListPlaceholder the obvious way ... 
} 

// to be used as 

SQLiteTable::insert(mName(record.name), mValue(record.value)); 

इस टपल संस्करण की तुलना में अधिक पठनीय है की तरह कुछ कोशिश कर सकते हैं: पहला, खेतों गिनती मूल्यों की गणना करने के लिए स्वचालित रूप से बराबर है, तो, प्रत्येक मान अपने क्षेत्र के बगल में आता है, यह, 'डिफ़ॉल्ट' फ़ील्ड मान (, के माध्यम से कहते हैं, mName()) का समर्थन करता है ...


query() के बारे में, अधिक अर्थपूर्ण विकल्प मीटर ight

// return a lazily evaluated input-range (or a coroutine?) 
for(auto item: SQLiteTable::query(mName, mValue)) 
    // ... 

// and/or query() simply returns a forwarding wrapper exposing, say, a chainable interface ... 
SQLiteTable::query(mName, mValue).for_each([]/*lambda*/); 
रुचि रखने वालों के लिए
+0

मुझे 'insert' के लिए आपका कार्यान्वयन पसंद है, धन्यवाद। 'क्वेरी' के बारे में, मैं @ बोल्व के समाधान को लागू करने की कोशिश कर रहा हूं लेकिन मैं मज़दूर को कॉल बनाने पर तैर गया हूं :-) धन्यवाद। – HappyCactus

संबंधित मुद्दे