基于C++14 compiles trait实现返回类型检查特性遇问题求助
问题描述
我找到一篇介绍C++14中实现Concepts的文章,其中给出了用于检查表达式是否可编译的compiles trait代码,但它不校验返回类型:
template <typename ... Ts> using void_t = void; template <typename T, template <typename> class Expression, typename AlwaysVoid = void_t<>> struct compiles : std::false_type {}; template <typename T, template <typename> class Expression> struct compiles<T, Expression, void_t<Expression<T>>> : std::true_type {};
文章提到可以通过封装这个compiles trait,实现compiles_convertible_type(检查表达式返回类型可转换为目标类型)和compiles_same_type(检查表达式返回类型与目标类型完全相同),但没有给出示例。我尝试了以下实现,但无论测试场景如何,结果都返回true:
template <typename T, typename Result, template <typename> class Expression> struct compiles_convertible_type : compiles<T, Expression, void_t<std::is_convertible<Result, std::result_of<Expression<T>>>>> {}; template <typename T, typename Result, template <typename> class Expression> struct compiles_same_type : compiles<T, Expression, void_t<std::is_same<Result, std::result_of<Expression<T>>>>> {};
我推测问题出在is_same和is_convertible本身是合法的模板实例化,不管表达式返回类型是否符合要求,void_t总能推导成功,导致compiles一直匹配到true_type的特化版本。
测试代码与错误输出
测试代码
namespace memory { struct memory_block{}; } struct MyAllocator { memory::memory_block allocate_block(){return {};}; void deallocate_block(memory::memory_block){}; std::size_t next_block_size() const {return 0;}; }; struct MyBadAllocator { memory::memory_block allocate_block(){return {};}; void deallocate_block(memory::memory_block){}; void next_block_size() const {}; }; template <typename T> struct BlockAllocator_impl { template <class Allocator> using allocate_block = decltype(std::declval<Allocator>().allocate_block()); template <class Allocator> using deallocate_block = decltype(std::declval<Allocator>().deallocate_block(std::declval<memory::memory_block>())); template <class Allocator> using next_block_size = decltype(std::declval<const Allocator>().next_block_size()); using result = std::conjunction< compiles_convertible_type<T, memory::memory_block, allocate_block>, compiles<T, deallocate_block>, compiles_same_type<T, std::size_t, next_block_size> >; using has_allocate_block = compiles_convertible_type<T, memory::memory_block, allocate_block>; using has_deallocate_block = compiles<T, deallocate_block>; using has_next_block_size = compiles_same_type<T, std::size_t, next_block_size>; }; template <typename T> using BlockAllocator = typename BlockAllocator_impl<T>::result; template <typename T> using BlockAllocatorAllocate = typename BlockAllocator_impl<T>::has_allocate_block; template <typename T> using BlockAllocatorDeallocate = typename BlockAllocator_impl<T>::has_deallocate_block; template <typename T> using BlockAllocatorNextBlockSize = typename BlockAllocator_impl<T>::has_next_block_size; #include <fmt/core.h> int main() { fmt::print("MyBadAllocator\n"); fmt::print("has allocate: {}\n", BlockAllocatorAllocate<MyBadAllocator>::value); fmt::print("has deallocate: {}\n", BlockAllocatorDeallocate<MyBadAllocator>::value); fmt::print("has next block size: {}\n", BlockAllocatorNextBlockSize<MyBadAllocator>::value); fmt::print("Is BlockAllocator: {}\n", BlockAllocator<MyBadAllocator>::value); fmt::print("MyAllocator\n"); fmt::print("has allocate: {}\n", BlockAllocatorAllocate<MyAllocator>::value); fmt::print("has deallocate: {}\n", BlockAllocatorDeallocate<MyAllocator>::value); fmt::print("has next block size: {}\n", BlockAllocatorNextBlockSize<MyAllocator>::value); fmt::print("Is BlockAllocator: {}\n", BlockAllocator<MyAllocator>::value); }
错误输出
MyBadAllocator has allocate: true has deallocate: true has next block size: true // 预期为false Is BlockAllocator: true // 预期为false MyAllocator has allocate: true has deallocate: true has next block size: true Is BlockAllocator: true
我需要正确的compiles_convertible_type和compiles_same_type实现,以实现对表达式返回类型的检查。
正确实现方案
核心问题是你把类型检查放在了void_t的参数里,而is_same/is_convertible本身是合法的模板,不会触发SFINAE。正确的做法是将类型检查嵌入到表达式模板中,让不符合要求的情况直接导致模板推导失败。
1. 基于原compiles trait的实现(推荐)
这种方式复用了文章中已有的compiles逻辑,结构更统一:
compiles_convertible_type实现
// 辅助模板:包装表达式并添加可转换性检查 template <typename Result, template <typename> class Expression> struct ConvertibleExpr { template <typename T> // 只有当Expression<T>能转换为Result时,这个类型才合法 using type = decltype(static_cast<Result>(std::declval<Expression<T>>())); }; template <typename T, typename Result, template <typename> class Expression> struct compiles_convertible_type : compiles<T, ConvertibleExpr<Result, Expression>::template type> {};
compiles_same_type实现
// 辅助模板:包装表达式并添加类型完全匹配检查 template <typename Result, template <typename> class Expression> struct SameTypeExpr { template <typename T> // 只有当Expression<T>与Result完全相同时,这个类型才合法 using type = typename std::enable_if< std::is_same<Expression<T>, Result>::value, void >::type; }; template <typename T, typename Result, template <typename> class Expression> struct compiles_same_type : compiles<T, SameTypeExpr<Result, Expression>::template type> {};
2. 独立实现版本(不依赖原compiles)
如果你不想依赖原compiles trait,可以直接用SFINAE实现:
compiles_convertible_type实现
template <typename T, typename Result, template <typename> class Expression> struct compiles_convertible_type { private: // 优先匹配:当Expression<T>可转换为Result时,返回true_type template <typename U> static auto test(int) -> decltype( static_cast<Result>(std::declval<Expression<U>>()), std::true_type{} ); // 兜底匹配:其他情况返回false_type template <typename U> static std::false_type test(...); public: static constexpr bool value = decltype(test<T>(0))::value; };
compiles_same_type实现
template <typename T, typename Result, template <typename> class Expression> struct compiles_same_type { private: // 优先匹配:当Expression<T>与Result完全相同时,返回true_type template <typename U> static auto test(int) -> typename std::enable_if< std::is_same<Expression<U>, Result>::value, std::true_type >::type; // 兜底匹配:其他情况返回false_type template <typename U> static std::false_type test(...); public: static constexpr bool value = decltype(test<T>(0))::value; };
测试结果
替换实现后,运行测试代码会得到预期输出:
MyBadAllocator has allocate: true has deallocate: true has next block size: false Is BlockAllocator: false MyAllocator has allocate: true has deallocate: true has next block size: true Is BlockAllocator: true
内容的提问来源于stack exchange,提问作者tderensis
相关产品推荐
相关产品推荐

