简体   繁体   中英

C++ Template Call member function if matching type else throw exception?

Consider the following class:

class Example {
    const void * pValue;
    (Example& (*SIndex(const void *,char *)));
    (Example& (*NIndex(const void *,unsigned long long *)));

    template<class T,typename I> static  Example & Index(const T* value,I index)
    { return Example(value->operator[](index)); }

public:
    template<class T> Example(const T& value) {
        pValue = &value;
        SIndex = (Example& (*(const void *,char *)))Index<T,char *>;
        NIndex = (Example& (*(const void *,unsigned long long)))Index<T,unsigned long long>;
    };
    Example& operator[](char * index) { return SIndex(pValue,index); };
    Example& operator[](unsigned long long) { return NIndex(pValue,index); };
}

I wish to use SFINAE (Substitution failure is not an error) (see http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error ), to alter _Index such that if there is no matching operator for the class it will throw an exception rather than return a new Example object. ie for _Index<T,char *> will match operator[](char *) or operator[](std::string&) as std::string can be constructed from char * while _Index<T,unsigned long long> will match any numerical type, as unsigned long long can be converted.

I have only just come across SFINAE while looking for solutions to this problem it clear the solution would lay in this, however I'm not sure how to implement it.

Example (As Requested): This will compile, currently the output is the pointer value no content, but that is fixable, Also I disabled the String Index Option as that causes a compiler error with out working SFINAE. Note it real world application obj1 and obj2 may be different types ie vector and map however will produce errors until SFINAE is added to the code to produce exceptions instead. Also the "index path" to open will be detriment at run time NOT compile time. Note: I'm working on extending this code https://github.com/vitaut/format to allow ids of the type {0[4][first]} or {1[error]} etc. Since not all object support these methods the object not supporting it should produce an runtime exception when the format string asks for it, also must not produce compiler errors, as any type used as an argument will be passed in to the Arg class, Example, just shows the part of the More complex Arg class I need to modify to get this working.

#include <iostream>
#include <string>
#include <vector>
#include <map>

using namespace std;

class Example {
    void * pValue;
    /*Example& (*SIndex)(void*,const char *);*/
    Example& (*NIndex)(void*,unsigned int);
    template<class T,typename I> static Example& Index(T* obj,I index) {
        return *(new Example((*obj)[index])); }
public:
    Example(char value) { pValue = &value; }
    template<class T> Example(T& value) {
        pValue = &value;
        /*SIndex = (Example& (*)(void*,const char *))Index<T,const char *>;*/
        NIndex = (Example& (*)(void*,unsigned int))Index<T,unsigned int>;
    }
    /*Example& operator[](const char * index) { return SIndex(pValue,index); }*/
    Example& operator[](unsigned int index) { return NIndex(pValue,index); }
    void * Get() { return pValue; }
};

int main()
{
    vector<Example> Objs;
    vector<string> obj1;
    vector<string> obj2;
    obj1.push_back("Hello ");
    obj1.push_back("World");
    obj2.push_back("Olleh ");
    obj2.push_back("Dlorw");
    Objs.push_back(Example(obj1));
    Objs.push_back(Example(obj2));
    cout << (string *)(Objs[0][0].Get()) << endl;
    cout << (string *)(Objs[1][1].Get()) << endl;
    cin.ignore();
    return 0;
}

Example 2 (with Simple's SFINALE code) v2:

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <utility>
#include <type_traits>

using namespace std;

template<typename T, typename I>
decltype(void((*declval<T>())[declval<I>()]), true_type())
has_subscript_test(int);

template<typename, typename>
false_type
has_subscript_test(...);

template<typename T, typename I>
struct has_subscript : decltype(has_subscript_test<T, I>(0))
{
};

class Example {
    void * pValue;
    Example& (*SIndex)(void*,const char *);
    Example& (*NIndex)(void*,unsigned int);
    template<class T,typename I> static
    typename enable_if<has_subscript<T, I>::value, Example&>::type
    Index(T* obj,I index) { return *(new Example((*obj)[index])); }
    template<class T,typename I> static
    typename enable_if<!has_subscript<T, I>::value, Example&>::type
    Index(T* obj,I index) { throw "Invalid Index Type"; }
public:
    //Example(char value) { pValue = &value; }
    template<class T> Example(T& value) {
        pValue = &value;
        SIndex = (Example& (*)(void*,const char *))Index<T,const char *>;
        NIndex = (Example& (*)(void*,unsigned int))Index<T,unsigned int>;
    }
    Example& operator[](const char* index) { return SIndex(pVale,index); }
    Example& operator[](unsigned int index) { return NIndex(pValue,index); }
    void * Get() { return pValue; }
};

int main()
{
    vector<Example> Objs;
    vector<string> obj1;
    map<string,string> obj2;
    obj1.push_back("Hello ");
    obj1.push_back("World");
    obj2["A"] = "Olleh ";
    obj2["B"] = "Dlorw";
    Objs.push_back(Example(obj1));
    Objs.push_back(Example(obj2));
    cout << (string *)(Objs[0][(unsigned int)0].Get()) << endl;
    cout << (string *)(Objs[1][(const char *)"B"].Get()) << endl;
    cin.ignore();
    return 0;
}

compiler errors: Looks like only the first 2 are important the rest are a side effect of those 2.

g++.exe -Wall -fexceptions  -std=c++11 -g     -c C:/SourceCode/Test/main.cpp -o ../obj/Debug/main.o
cygwin warning:
  MS-DOS style path detected: C:/SourceCode/Test/main.cpp
  Preferred POSIX equivalent is: /cygdrive/c/SourceCode/Test/main.cpp
  CYGWIN environment variable option "nodosfilewarning" turns off this warning.
  Consult the user's guide for more details about POSIX paths:
    http://cygwin.com/cygwin-ug-net/using.html#using-pathnames
C:/SourceCode/Test/main.cpp: In instantiation of ‘static typename std::enable_if<has_subscript<T, I>::value, Example&>::type Example::Index(T*, I) [with T = std::vector<std::basic_string<char> >; I = const char*; typename std::enable_if<has_subscript<T, I>::value, Example&>::type = Example&]’:
C:/SourceCode/Test/main.cpp:37:9:   required from ‘Example::Example(T&) [with T = std::vector<std::basic_string<char> >]’
C:/SourceCode/Test/main.cpp:54:32:   required from here
C:/SourceCode/Test/main.cpp:29:64: error: invalid conversion from ‘const char*’ to ‘std::vector<std::basic_string<char> >::size_type {aka unsigned int}’ [-fpermissive]
In file included from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/vector:65:0,
                 from C:/SourceCode/Test/main.cpp:3:
/usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/bits/stl_vector.h:750:7: error:   initializing argument 1 of ‘std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::operator[](std::vector<_Tp, _Alloc>::size_type) [with _Tp = std::basic_string<char>; _Alloc = std::allocator<std::basic_string<char> >; std::vector<_Tp, _Alloc>::reference = std::basic_string<char>&; std::vector<_Tp, _Alloc>::size_type = unsigned int]’ [-fpermissive]
C:/SourceCode/Test/main.cpp: In instantiation of ‘static typename std::enable_if<has_subscript<T, I>::value, Example&>::type Example::Index(T*, I) [with T = std::map<std::basic_string<char>, std::basic_string<char> >; I = unsigned int; typename std::enable_if<has_subscript<T, I>::value, Example&>::type = Example&]’:
C:/SourceCode/Test/main.cpp:38:9:   required from ‘Example::Example(T&) [with T = std::map<std::basic_string<char>, std::basic_string<char> >]’
C:/SourceCode/Test/main.cpp:55:32:   required from here
C:/SourceCode/Test/main.cpp:29:64: error: invalid user-defined conversion from ‘unsigned int’ to ‘std::map<std::basic_string<char>, std::basic_string<char> >::key_type&& {aka std::basic_string<char>&&}’ [-fpermissive]
In file included from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/string:54:0,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/bits/locale_classes.h:42,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/bits/ios_base.h:43,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/ios:43,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/ostream:40,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/iostream:40,
                 from C:/SourceCode/Test/main.cpp:1:
/usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/bits/basic_string.h:487:7: note: candidate is: std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>] <near match>
/usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/bits/basic_string.h:487:7: note:   no known conversion for argument 1 from ‘unsigned int’ to ‘const char*’
C:/SourceCode/Test/main.cpp:29:64: error: invalid conversion from ‘unsigned int’ to ‘const char*’ [-fpermissive]
In file included from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/string:54:0,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/bits/locale_classes.h:42,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/bits/ios_base.h:43,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/ios:43,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/ostream:40,
                 from /usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/iostream:40,
                 from C:/SourceCode/Test/main.cpp:1:
/usr/lib/gcc/i686-pc-cygwin/4.7.3/include/c++/bits/basic_string.h:487:7: error:   initializing argument 1 of ‘std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
C:/SourceCode/Test/main.cpp:29:64: error: conversion to non-const reference type ‘std::map<std::basic_string<char>, std::basic_string<char> >::key_type&& {aka class std::basic_string<char>&&}’ from rvalue of type ‘std::basic_string<char>’ [-fpermissive]

This should work:

template<typename T, typename I>
decltype(void((*std::declval<T>())[std::declval<I>()]), std::true_type())
has_subscript_test(int);

template<typename, typename>
std::false_type
has_subscript_test(...);

template<typename T, typename I>
struct has_subscript : decltype(has_subscript_test<T, I>(0))
{
};

class Example {
    const void * pValue;
    (Example& (*SIndex(const void *,char *)));
    (Example& (*NIndex(const void *,unsigned long long *)));

    template<class T, typename I> static
    typename std::enable_if<has_subscript<T, I>::value, Example&>::type
    _Index(const T* value,I index) { return Example((*value)[index]); }

    template<class T, typename I> static
    typename std::enable_if<!has_subscript<T, I>::value, Example&>::type
    _Index(const T*,I) { throw "uh-oh!"; }
public:
    template<class T> Example(const T& value) {
        pValue = &value;
        SIndex = (Example& (*(const void *,char *)))_Index<T,char *>;
        NIndex = (Example& (*(const void *,unsigned long long)))_Index<T,unsigned long long>;
    };
    Example& operator[](char * index) { return SIndex(this,index); };
    Example& operator[](unsigned long long) { return NIndex(this,index); };
};

I've done the SFINAE bit but there are other problems with your code. _Index is not allowed as a name, you're returning a temporary by reference and you're trying to cast a member pointer to a function pointer (even with a different function type!)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM