【问题标题】:SFINAE/enable_if based on the contents of a string parameter?SFINAE/enable_if 基于字符串参数的内容?
【发布时间】:2019-07-12 08:23:36
【问题描述】:

我无法理解以下问题。我什至真的不知道我该如何接近它。

考虑这段代码:

struct fragment_shader {
    std::string mPath;
};

struct vertex_shader {
    std::string mPath;
};

template <typename T>
T shader(std::string path) { 
    return T{ path };
}

要创建不同的结构,我可以编写以下内容:

auto fragmentShader = shader<vertex_shader>("some_shader.frag");
auto vertexShader = shader<fragment_shader>("some_shader.vert");

我想知道,是否可以让编译器根据传递给shader 函数的path 参数找出类型,所以我只需要写:

auto fragmentShader = shader("some_shader.frag");
auto vertexShader = shader("some_shader.vert");

由于文件以“.frag”结尾,类型fragment_shader会被推断出来,而对于以“.vert”结尾的路径,vertex_shader会被推断出来。

这可能吗?

我在enable_if 上阅读了一些内容,但实际上我不知道如何使用它来实现我想要实现的目标。我会尝试以下方法:

template<> 
typename std::enable_if<path.endsWith(".frag"), fragment_shader>::type shader(std::string path) {
    return fragment_shader{ path };
}

template<> 
typename std::enable_if<path.endsWith(".vert"), vertex_shader>::type shader(std::string path) {
    return vertex_shader{ path };
}

但显然,这不会编译。这只是为了说明我想要做什么。

【问题讨论】:

  • 模板不可能,因为它只是编译时的事情,字符串的内容只有在运行时才知道。但是,您可以使用标准工厂模式,其中函数返回一个指向公共基类的指针,并在运行时检查后缀以创建适当的对象。
  • 路径在编译时都知道吗?如果是,可能有一个解决方案……如果不是,它不能像这样工作,因为类型需要在编译时知道,因此不能仅依赖于运行时已知的东西。
  • 可以对编译时已知的路径进行重载。所以为了这个问题:让我们假设所有路径在编译时都是已知的。
  • 问题是即使是文字字符串在编译时也不知道,或者更确切地说,它们的 位置 是不知道的。这就是为什么你也不能使用指向它们的指针。文字字符串常量数组的位置只有在 link 时才知道。
  • @Someprogrammerdude 结果表明字符串文字在编译时是未知的,但显然具有静态链接的 char 数组是已知的。看我的回答。

标签: c++ c++17 sfinae enable-if


【解决方案1】:

如果在编译时知道所有路径,我有一个解决方案。事实证明,使用静态链接声明的固定大小 char 数组可以用作模板参数(与字符串文字相反),因此您可以根据模板参数使函数返回两种不同的类型:

这是一个辅助函数,可以在编译时确定文件结尾是否为.frag(您可能希望拥有.vert 的等效函数):

template <std::size_t N, const char (&path)[N]>
constexpr bool is_fragment_shader()
{
    char suf[] = ".frag";
    auto suf_len = sizeof(suf);

    if (N < suf_len)
        return false;

    for (int i = 0; i < suf_len; ++i)
        if (path[N - suf_len + i] != suf[i])
            return false;

    return true;
}

此函数根据文件结尾返回两种不同的类型。当您用C++17 标记问​​题时,我使用if constexpr 而不是enable_if,我发现它更具可读性。但是通过enable_if 进行两次重载也可以:

template <std::size_t N, const char (&path)[N]>
auto shader_impl()
{
    if constexpr (is_fragment_shader<N, path>())
        return fragment_shader{ path };
    else
        return vertex_shader{ path };
}

最后,要使用它,您需要这样做:

static constexpr const char path[] = "some_shader.frag"; // this is the important line
auto frag = shader_impl<sizeof(path), path>();

这当然有点烦人。如果您可以使用宏,您可以定义一个定义 lambda 并保存静态字符串并立即执行的宏,如下所示:

#define shader(p) \
[]{ \
    static constexpr const char path[] = p; \ // this is the important line
    return shader_impl<sizeof(path), path>(); \
}() \

那么调用语法就如你所愿:

auto frag = shader("some_shader.frag");
static_assert(std::is_same_v<decltype(frag), fragment_shader>);

auto vert = shader("some_shader.vert");
static_assert(std::is_same_v<decltype(vert), vertex_shader>);

请找到一个完整的示例here


编辑:

事实证明,如果 char 数组在全局命名空间中声明,MSVC 只允许它们作为模板参数,我能想到的最佳解决方案是在此处声明所有需要的路径。

static constexpr char some_shader_frag[] = "some_shader.frag";
static constexpr char some_shader_vert[] = "some_shader.vert";

如果你稍微改变宏,调用看起来仍然很漂亮(当然,必须在其他地方声明字符串仍然是一个很大的 PITA):

#define shader(p) \
[]{ \
    return shader_impl<sizeof(p), p>(); \
}() \

void test()
{
    auto frag = shader(some_shader_frag);
    static_assert(std::is_same_v<decltype(frag), fragment_shader>);

    auto vert = shader(some_shader_vert);
    static_assert(std::is_same_v<decltype(vert), vertex_shader>);
}

看到它工作here


编辑 2:

此问题已在 VS 2019 版本 16.4 (msvc v19.24) 中修复:https://developercommunity.visualstudio.com/content/problem/341639/very-fragile-ice.html

看到它在工作here

【讨论】:

  • 哇,这看起来很有前途!在您引入宏之前,您提出的解决方案对我有效。我得到的错误错误是“模板参数可能不引用非外部实体”,这意味着我不允许将非全局字符数组作为模板参数传递。我只能将字符数组传递给在全局范围内定义的模板。有任何想法吗? (我正在使用启用了最新功能集的 MSVC,即 C++17)
  • @j00hi 不幸的是,我只使用 clang 和 gcc 对其进行了测试,但我可以使用 msvc 重现您的错误。我已经更新了我的答案。我对它不是 100% 满意,但总比没有好,我猜。
  • @j00hi 我问了关于这个问题的另一个问题:stackoverflow.com/q/57009060/9883438 显然,这是 MSVC 中的一个错误。所以,也许你很幸运,它很快就会得到修复。
【解决方案2】:

这可能吗?

简短回答:不。

长答案。

C++ 是一种静态类型语言,编译器需要在编译时决定函数的返回类型。

在你的情况下

auto fragmentShader = shader("some_shader.frag");
auto vertexShader = shader("some_shader.vert");

您试图从同一个函数中获取两种不同的返回类型,并根据运行时已知值确定返回类型。

我知道"some_shader.frag" 是在编译时已知的char const [17],但问题是shader() 也接收std::string 仅在运行时已知

std::string s;

std::cin >> s;

auto foo = shader(s);  // which type, in this case, at run-time ?

【讨论】:

  • 您可以在编译时执行各种操作,例如对constexpr 声明的数组进行排序,如constexpr initialization of array to sort contents 所示。所以我想知道是否也可以在编译时评估一个字符串。它必须是 constexpr 字符串。也许我应该将此添加到我的问题中。
  • @j00hi:你仍然不能从同一个函数有两种不同的返回类型,你不能推导出一个模板参数,它依赖于一个字符串文字的长度。您可以编写字符串(文字)的constexpr 函数并将结果 as 用作模板参数,但您仍然不能将文字本身用作模板参数以避免重复(尽管 C ++20 允许通过将其包装在类类型对象中的技巧来实现几乎等效的东西)。
  • @j00hi - 你可以在编译时做很多事情,但同一函数没有不同的返回类型。您不能拥有constexpr std::string(目前),但可以拥有constexpr std::string_view(来自C++17)或consexpr 字符串文字(即char const [someDim])。但问题是您应该将constexpr 元素作为模板参数传递;这样,函数签名会发生变化,您可以拥有具有不同返回类型的不同函数。您不能将 constexpr 值作为普通函数参数传递,从而更改返回类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-26
相关资源
最近更新 更多