【问题标题】:C++ Template Call member function if matching type else throw exception?C++模板调用成员函数如果匹配类型否则抛出异常?
【发布时间】:2013-12-05 12:41:38
【问题描述】:

考虑以下类:

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); };
}

我希望使用 SFINAE(替换失败不是错误)(参见 http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error)来更改 _Index,这样如果类没有匹配的运算符,它将抛出异常而不是返回新的 Example 对象。即_Index&lt;T,char *&gt; 将匹配operator[](char *)operator[](std::string&amp;),因为std::string 可以从char * 构造,而_Index&lt;T,unsigned long long&gt; 将匹配任何数字类型,因为unsigned long long 可以转换。

我只是在寻找这个问题的解决方案时遇到了 SFINAE,很明显解决方案就在这个问题上,但是我不确定如何实现它。

示例(根据要求): 这将编译,当前输出是指针值没有内容,但这是可修复的,另外我禁用了字符串索引选项,因为这会导致编译器错误,而 SFINAE 无法正常工作。请注意,现实世界的应用程序 obj1 和 obj2 可能是不同的类型,即 然而,vector 和 map 将产生错误,直到将 SFINAE 添加到代码中以产生异常。此外,打开的“索引路径”将在运行时而不是编译时受到损害。 注意:我正在扩展此代码 https://github.com/vitaut/format 以允许 {0[4][first]} 或 {1[error]} 等类型的 id。由于并非所有对象都支持这些方法,因此不支持它的对象应该当格式字符串要求时产生运行时异常,也不得产生编译器错误,因为用作参数的任何类型都将传递给 Arg 类,例如,只显示了我需要的更复杂的 Arg 类的一部分修改以使其正常工作。

#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;
}

示例 2(使用 Simple 的 SFINALE 代码)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;
}

编译器错误: 看起来只有前 2 个很重要,其余的都是这 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]

【问题讨论】:

  • 注意:您的代码在技术上是非法的。以下划线后跟大写字母的名称(以及包含两个连续下划线的名称)保留用于实现(编译器和标准库)。不得在用户代码中使用它们。
  • 如果能在编译时检测到,为什么还要编译然后抛出运行时异常?在某些情况下这是有效的,但你在其中吗?
  • 是的,我正在扩展一个字符串格式化库,以提供参数的下标,如果对象不支持它,它应该抛出异常,而不是崩溃,但该函数必须存在于任何类型传递给字符串格式化程序,因此其无效调用该函数表示异常/运行时错误
  • 我支持 Yakk:这不应该是编译时错误吗?在我看来,将浮点数传递给 [] 是语法错误,而不是语义错误。也就是说,为什么传递浮点数的代码不被视为错误代码,而不是无效的运行时状态?
  • 如下所述,以上代码无法编译。为了让我们知道你想要什么,你能写一个sscce.org 编译并适用于你不希望它抛出的情况吗?我会假设对于您的 sscce.org 没有编译的任何类型,您希望它抛出...

标签: c++ class templates template-meta-programming sfinae


【解决方案1】:

这应该可行:

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); };
};

我已经完成了 SFINAE 位,但您的代码还有其他问题。 _Index 不允许作为名称,您正在通过引用返回一个临时值,并且您正在尝试将成员指针强制转换为函数指针(即使使用不同的函数类型!)

【讨论】:

  • 这不是只有在 T[index] 返回 Example 的对象时才有效吗?示例对象应该通过返回值用于构造新的示例对象这一事实来创建。假设 T 是 std::map<:string> 其中 X 是其他类,SIndex 应该执行 Example(X),而 NIndex 应该抛出错误。那这段代码还能用吗?
  • 我实际上只是将 SFINAE 添加到您已经存在的代码中,我认为它可以满足您的需求。它现在应该和以前一样工作,只是添加了抛出异常。请注意我指出的代码中有错误。
  • 哦是的 _Index 是静态的,代码只是我快速写的一个例子来演示我正在尝试做的事情,我的实际代码更复杂。
  • @glenflet 即使在将_Index 设为static 之后,您仍在转换为错误的函数类型(我不明白您为什么需要SIndexNIndex... ) 然后通过那个不正确的函数指针调用它。一个很大的禁忌。
  • 我知道指针类型是错误的,但是 CPU 只关心编译器指针只是一个内存位置基本上我告诉编译器指针实际上是一个 void * 然后稍后我告诉它is 实际上是一个 T*,因为我知道指针实际上指向一个 T*,请注意类存储作为 void 指针传递给构造函数的任何对象,这就是我需要 SIndex 和 NIndex 因为它们将指向一个函数从特定类的模板生成。我的代码实际上使用这些数组来表示一个 arbarty 对象数组。
猜你喜欢
  • 1970-01-01
  • 2020-09-11
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
  • 2017-12-24
  • 2015-12-14
  • 2019-01-04
  • 1970-01-01
相关资源
最近更新 更多