用于OpenGL调用的c++11包装器
c++11 wrappers for OpenGL calls
大多数OpenGL API调用都不会返回值。有了C++11完美的转发,就可以很容易地编写一个错误检查包装器函数模板和可变宏,比如
template <typename F, typename ...Args>
void call(
const char *text,
int line,
const char *file,
F && f, Args &&... args
)
{
std::forward<F>(f)(std::forward<Args>(args)...);
auto err = glGetError();
if (err != 0) _throw_error(text, err, line, file);
}
#define GL(fn, ...) call(#fn, __LINE__, __FILE__, gl##fn, __VA_ARGS__)
这是有效的,并允许调用这样写:
GL(Viewport, 0, 0, 1920, 1080);
但是,一些OpenGL函数确实返回了一个值,这使得包装函数在检查错误之前必须将返回值存储在临时变量中。带有返回值的调用的包装器函数模板可以这样写:
template <typename F, typename ...Args>
auto call(
const char *text,
int line,
const char *file,
F && f, Args &&... args
)
{
auto && res = std::forward<F>(f)(std::forward<Args>(args)...);
auto err = glGetError();
if (err != 0) _throw_error(text, err, line, file);
return res;
}
(相同的宏可用于调用此表单。)
问题是,无法定义上述函数模板中的两个,因为对于所有返回类型为void
的API调用,两个模板都将匹配,从而导致"对重载函数的调用不明确"错误(Visual C++)。
当然,可以为这两种形式定义单独的宏,但这并不优雅。GL
是包装宏名称的自然、明显和正确的选择,任何额外的宏(如GLR
)都会显得很难看,很难记住。
特别是在发布版本中,您不希望在每次调用后都调用glGetError
。这对性能没有好处,而且glGetError
无论如何都不会返回描述性错误(GL_INVALID_OPERATION
用于很多事情)。
相反,为了进行调试,请创建一个调试上下文,并使用glDebugMessageCallback
注册一个回调,该回调将打印出错误消息。这是一个相对较新的OpenGL功能,但根据我的经验,它提供了更好的消息。
void debug_callback(GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
printf("%s", message);
}
glDebugMessageCallback(&debug_callback, nullptr);
上述问题的解决方案并不困难,但我惊讶地发现,Visual C++不会接受我使用Coliru(使用gcc)制定并验证的解决方案(这意味着它被内部编译器错误卡住了)。
当然,需要的是SFINAE,而C++11通过非常方便的std::enable_if<>
支持它。适用于gcc但不适用于Visual Studio 2015的解决方案是:
template <typename F, typename ...Args,
typename R = std::enable_if_t<!std::is_void<std::result_of_t<F(Args...)>>::value, std::result_of_t<F(Args...)>>>
>
R call(const char *text, int line, const char *file, F && f, Args &&...args)
{...}
(类似于void函数,使用std:is_void<...>::value
)。
经过一番搜索,我找到了以下可以编译的内容:
template <typename F, typename ...Args>
auto call(
std::enable_if_t<!std::is_void<std::result_of_t<F(Args...)>>::value, const char> *text,
int line,
const char *file,
F && f, Args &&... args
)
{
auto && res = std::forward<F>(f)(std::forward<Args>(args)...);
auto err = glGetError();
if (err != 0) _throw_error(text, err, line, file);
return res;
}
和
template <typename F, typename ...Args>
void call(
std::enable_if_t<std::is_void<std::result_of_t<F(Args...)>>::value, const char> *text,
int line,
const char *file,
F && f, Args &&... args
)
{
std::forward<F>(f)(std::forward<Args>(args)...);
auto err = glGetError();
if (err != 0) _throw_error(text, err, line, file);
}
这个解决方案唯一值得注意的是它使用SFINAE的方式。std::enable_if_t<>
不仅用于消除不必要的专门化,而且还声明了宏传入的text
参数的类型。因此,我没有通过一个带有默认值的额外的、未使用的参数来执行SFINAE(我尝试过,但由于某种原因,在Visual Studio下也会导致"内部编译器错误"),而是在我无论如何都需要的三个额外参数中的一个上使用了它。
- 如何在c++17中制作一个模板包装器/装饰器
- std::vector的包装器,使数组的结构看起来像结构的数组
- 如何在c++迭代器类型中包装std::chrono
- 标准向量之上的 C++11 包装类
- C 11 / 14-是否有针对其他地方管理的资源的原始指针包装器
- 通用 C++11 函数包装器,用于基于任务的并行性
- 函数包装器初始化在 C++11 中是如何工作的
- 用C++11中的智能指针包装旧的C结构并自动释放它们
- c++11:如何编写一个包装函数来生成"std::function"对象
- C++11/14:如果函数存在,则对其进行包装
- 访问向量 c++11 中的引用包装元素
- C++11 模板函数别名与包装器
- C++ Boost/C++11 的包装器
- 将 c++11 std::function 包装在另一个 std::function 中
- C++11:unique_ptr抱怨类型不完整,但当我包装它时却没有
- 是否使用类包装器对符合 C++11 的对象进行线程安全访问
- 使用 C++11 接口包装 C 回调的最佳方法是什么?
- 用于OpenGL调用的c++11包装器
- c++11模板特化包装器
- C++11 std::函数和std::reference包装器,用于对std::set进行排序