SFINAE / enable_if是否基于字符串参数的内容?

时间:2019-07-12 08:23:36

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

我无法解决以下问题。我什至都不知道该怎么办。

考虑以下代码:

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

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

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

但是显然,这不能编译。只是为了弄清楚我要做什么。

2 个答案:

答案 0 :(得分:4)

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

这是一个帮助函数,可以在编译时确定文件结尾是否为.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


编辑:

事实证明,如果MSVC仅在全局命名空间中声明char数组作为模板参数,我想到的最好的解决方案就是在此声明所有需要的路径。

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

答案 1 :(得分:0)

  

有可能吗?

简短回答:否。

长答案。

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 ?