TiledArray::conj and TiledArray::conj_to are two independent ADL customization points in tile_op/tile_interface.h, and the default conj_to is SFINAE-constrained on a conj_to() member:
template <typename Arg> // value-returning
inline auto conj(const Arg& arg) { return arg.conj(); }
template <typename Result,
typename = std::enable_if_t<
detail::has_member_function_conj_to_anyreturn_v<Result&&>>>
inline decltype(auto) conj_to(Result&& result) { // in-place
return std::forward<Result>(result).conj_to();
}
A custom tile that implements value-returning conj but no conj_to is therefore a legal partial implementation of the tile interface — the pattern AGENTS.md/CLAUDE.md explicitly blesses (tests/sparse_tile.h's EigenSparseTile omits subt for the same reason) — yet it cannot be used in a conjugated contraction.
The asymmetry
Grepping for consumers: conj and conj_to have exactly one call site each in the whole library, and it is the same function — ContractReduce's ComplexConjugate finalization — picking between them on whether a result permutation is present:
src/TiledArray/tile_op/contract_reduce.h conj_to(temp) // unpermuted
src/TiledArray/tile_op/contract_reduce.h conj(temp, perm) // permuted
So for such a tile:
c("k,i") = conj(a("i,j") * b("j,k")); // compiles (permuted -> conj)
c("i,k") = conj(a("i,j") * b("j,k")); // hard error (unpermuted -> conj_to)
The same operation compiles or not depending on whether the caller happened to permute the result. Unary conj(a("i,j")) is unaffected — it is a Scal expression with a ComplexConjugate factor and goes through scale/scale_to, not these CPOs.
Scope
Latent, not live: every in-tree tile supplies conj_to — TA::Tensor, TensorInterface, ArenaTensor, TA::Tile (forwards to the wrapped tensor), and btas::Tensor. This predates #574, which moved the two ComplexConjugate specializations behind one if constexpr without touching the dispatch.
Fix
Prefer in-place conj_to when it is viable and fall back to value-returning conj otherwise, keeping the in-place path for Tensor/ArenaTensor/Tile/btas::Tensor. Both unpermuted branches need it (conj_to(temp) and conj_to(temp, factor)).
Trap to avoid: the detector must test the ADL expression conj_to(temp), not detail::has_member_function_conj_to_anyreturn_v. btas::Tensor has no conj_to member — only free functions in namespace btas (external/btas.h) — so a member-based detector would classify BTAS as lacking conj_to and silently demote it from in-place conjugation to an allocate/copy/free per tile. That is a performance regression that would pass every existing test.
Wants a regression test with a tile type that provides conj and no conj_to, plus a check that BTAS still takes the in-place path.
TiledArray::conjandTiledArray::conj_toare two independent ADL customization points intile_op/tile_interface.h, and the defaultconj_tois SFINAE-constrained on aconj_to()member:A custom tile that implements value-returning
conjbut noconj_tois therefore a legal partial implementation of the tile interface — the patternAGENTS.md/CLAUDE.mdexplicitly blesses (tests/sparse_tile.h'sEigenSparseTileomitssubtfor the same reason) — yet it cannot be used in a conjugated contraction.The asymmetry
Grepping for consumers:
conjandconj_tohave exactly one call site each in the whole library, and it is the same function —ContractReduce'sComplexConjugatefinalization — picking between them on whether a result permutation is present:So for such a tile:
The same operation compiles or not depending on whether the caller happened to permute the result. Unary
conj(a("i,j"))is unaffected — it is aScalexpression with aComplexConjugatefactor and goes throughscale/scale_to, not these CPOs.Scope
Latent, not live: every in-tree tile supplies
conj_to—TA::Tensor,TensorInterface,ArenaTensor,TA::Tile(forwards to the wrapped tensor), andbtas::Tensor. This predates #574, which moved the twoComplexConjugatespecializations behind oneif constexprwithout touching the dispatch.Fix
Prefer in-place
conj_towhen it is viable and fall back to value-returningconjotherwise, keeping the in-place path forTensor/ArenaTensor/Tile/btas::Tensor. Both unpermuted branches need it (conj_to(temp)andconj_to(temp, factor)).Trap to avoid: the detector must test the ADL expression
conj_to(temp), notdetail::has_member_function_conj_to_anyreturn_v.btas::Tensorhas noconj_tomember — only free functions in namespacebtas(external/btas.h) — so a member-based detector would classify BTAS as lackingconj_toand silently demote it from in-place conjugation to an allocate/copy/free per tile. That is a performance regression that would pass every existing test.Wants a regression test with a tile type that provides
conjand noconj_to, plus a check that BTAS still takes the in-place path.