I would say there is a relationship, but it's indirect.
Sequence points are how the language defines the sequenced before partial ordering on evaluations (C17 5.1.2.3), which is basically what you might generically call "program order". It's the order that operations naively appear to be executed in, and for a single-threaded program, it completely determines how those operations interact with each other. It promises, for instance, that if you call foo() && bar(), that bar will see all the updates to global variables that were made by foo; the evaluation of foo() will be complete before the evaluation of bar() begins.
Now the whole point of memory ordering is that the order in which loads and stores are seen by other threads does not necessarily agree with program order. If you write
res = atomic_load_explicit(&a, memory_order_relaxed) || atomic_load_explicit(&b, memory_order_relaxed);
then the load of a is sequenced before the load of b, thanks to the sequence point provided by ||. But because the memory order is relaxed, that is no guarantee that other threads see those operations in the same order. If we previously had a == 0 and b == 1, and another thread does atomic_store(&a, 1); atomic_store(&b, 0); (with seq_cst order, let's say) then it is entirely possible that res ends up with the value 0.
That's the same as the situation with your example code. You have a sequence point between the fetch_sub and the load. (Actually two of them: one at the end of the full expression that initializes counter, and the other at the && which you pointed out.) So the fetch_sub is most definitely sequenced before the load. But your memory ordering isn't strong enough to promise that inter-thread visibility matches sequencing.
However, memory barriers can give you more certainty, and what they do is to ensure that, to some specified extent, the order of visibility to outside observers does agree with program order. If you write
res = atomic_load_explicit(&a, memory_order_seq_cst) || atomic_load_explicit(&b, memory_order_seq_cst);
then the sequence point again guarantees that the load of a is sequenced before the load of b, and now the seq_cst guarantees that other threads see those operations in that order too. Now res will always be set to 1 if another thread does atomic_store(&a, 1); atomic_store(&b, 0);.
But memory barriers only impose, at best, as much ordering as the sequencing already provided. If you instead did
res = atomic_load_explicit(&a, memory_order_seq_cst) + atomic_load_explicit(&b, memory_order_seq_cst);
now the barriers do not help you. There is no sequence point between the two loads, and therefore no guarantees whatsoever about the order in which they are even evaluated, much less about the order in which they become visible.
Formally, the sequenced before relation is used in defining the happens before relation of 5.1.2.4, which is the main tool in determining how loads and stores from different threads affect each other, and in particular, in determining when they do and don't cause data races.