You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 00:10:33